You open a clock. You switch tabs. You come back thirty seconds later.

The time is right. But you never saw it tick. The display didn’t move while you were away. It jumped.

This isn’t a bug. Browser developers made a deliberate choice to save your battery. Once you understand why, you’ll stop blaming the site and start blaming the resource management baked into every modern client.

Here’s what’s actually happening under the hood.

What happens to timers in background tabs

Every tab runs JavaScript. That JavaScript can fetch data, animate elements, update the DOM. But most of that work is invisible when you’re not looking at the tab. Why waste CPU cycles rendering something nobody can see?

Engineers reached the same conclusion. Chrome began aggressive background tab throttling around 2017. Hidden tabs had their timers throttled to once per second: a 1Hz limit. Other clients followed with similar policies.

The result? setInterval and setTimeout no longer fire at their requested frequency in background tabs. A clock updating every 50 milliseconds suddenly updates once per second. Or less often, depending on the client’s mood and your system’s power state.

Why background tab throttling exists

One reason. Battery life.

Laptop batteries are finite. Every CPU cycle a background tab consumes is a cycle that doesn’t go toward your actual work. Ten tabs running timers at full speed would spin up your fan, drain your battery, and push you to close the whole window in frustration.

So the trade was made. Background tabs get fewer CPU slices. Timers fire less often. Your laptop lasts longer.

This is by design. Not a bug. Remember that the next time your clock seems slow.

requestAnimationFrame pauses in hidden tabs

requestAnimationFrame is the API used for smooth animations. It fires before a paint. On a 60Hz display, that’s roughly every 16.7 milliseconds.

But there’s a catch. If the tab is hidden, there’s nothing to paint. The animation frame gets skipped entirely. Across all major clients, requestAnimationFrame pauses completely in hidden tabs.

This is exactly why modern clocks use it. The clock updates only when the client is ready to draw a frame. Switch away, updates stop. Return, they resume. No wasted work. No invisible rendering.

setInterval and setTimeout throttling (1Hz limit)

setInterval and setTimeout get a different treatment. Instead of pausing entirely, they’re throttled. Chrome’s aggressive throttling limits hidden tab timers to once per second.

Here’s the critical detail: the client doesn’t queue up all the missed callbacks. It doesn’t fire 20 callbacks after you’ve been away for 20 seconds. It fires one. The intermediate ticks are lost. The clock updates once, reading the current time, and that’s it.

This is why a clock might skip 15 seconds instead of showing each one. The client collapsed the missed updates into a single tick.

How live clocks recover when you return

The clock is never wrong. It’s just late.

When you return to the tab, the clock recalculates from system time. Date.now() reads the operating system’s clock. If your system clock is correct, the displayed time is correct. The seconds didn’t pass in the background, but the time did. The clock just didn’t show it.

The visual update pauses. The time source never stops. That’s why the clock is right when you come back. It’s not catching up. It’s simply reading the current time and drawing it.

How the visibility API helps clocks handle background tab timer throttling

The Page Visibility API lets sites detect whether they’re visible or hidden. document.visibilityState returns "visible" or "hidden". The visibilitychange event fires when that state changes.

Good clock implementations use this. They know when they’re hidden. They can pause expensive work, save state, and resume cleanly when visible again.

A well-built clock checks visibility. It doesn’t fight the throttling. It works with it.

Why clocks skip seconds: the most common cause

The most common cause of a skipping clock isn’t throttling at all. It’s frame drops.

requestAnimationFrame is not guaranteed to fire at exact intervals. The client may skip frames under load. If your CPU is busy, a video encoding, a large page rendering, a background sync, the client may miss a frame. The clock jumps forward. The seconds skip.

This happens even in visible tabs. It’s not a bug. It’s the client prioritizing what matters. If it can’t render a frame on time, it drops it. The clock doesn’t display the missed second. It shows the next one.

System load and frame drops

System load makes this worse. A busy CPU means fewer frames.

Fewer frames means a choppier clock. This is physics, not poor programming.

If your clock skips seconds while the tab is visible, check your CPU usage. A runaway process will do that. Close the tab you’re not using. Give the client room to breathe.

And if you need a clock that never skips, use a dedicated device. A client is not a timekeeping instrument. It’s a rendering engine that happens to display time.

Workarounds and their limitations

Can you force a client to run timers in the background? Not reliably. Web Workers have their own throttling policies. The Screen Wake Lock API keeps the screen on but doesn’t prevent timer throttling. Fullscreen mode changes the display, not the timer behavior.

The honest answer: if you need a clock that updates every frame, keep the tab visible. That’s it. That’s the workaround.

For most people, this doesn’t matter. A clock that updates once per second is fine. A clock that updates when you look at it is fine. The time is correct. The display is delayed. That’s the trade-off of running software in a client.