A dashboard refreshes with `setInterval(loadStats, 5000)`. What do browsers do to that interval once the tab has sat in the background for a while, and how should the page handle it?
answer
- hidden tabs are budgeted, not stopped
- seconds become minutes
- skipped ticks never replay
- frames stop entirely
- one fetch on return, not a burst
basics
~20 sBrowsers clamp timers in hidden pages — typically to about once a second, and Chromium drops to roughly once a minute after several minutes hidden. Missed ticks are not replayed. Stop polling on the hidden transition and refetch once when the page becomes visible.
solid answer
~50 sHidden pages get their timers throttled. Browsers clamp background-tab timers to roughly one per second, and Chromium applies a much harsher policy — about one wake-up per minute — once a page has been hidden and quiet for several minutes, with exemptions for things like audible playback. Requested ticks that are skipped are simply skipped: nothing queues up and replays when the tab returns. `requestAnimationFrame` is stronger still — a hidden page gets no frames at all. So a five-second poll silently becomes a one-minute poll, and the first thing the user sees on returning may be minutes-old data. The fix is not to fight the throttle with shorter intervals; it is to make the lifecycle explicit: on `visibilitychange` to hidden, clear the interval and stop polling; on the transition back to visible, fetch once immediately to repaint fresh data, then restart the interval. That saves battery, avoids a burst of stale requests, and makes the user's first glance correct.
code
javascript · 23 lineslet timerId = null;
async function refresh() {
const res = await fetch('/api/stats');
if (res.ok) render(await res.json());
}
function sync() {
if (document.visibilityState === 'hidden') {
clearInterval(timerId);
timerId = null;
return;
}
refresh();
if (timerId === null) timerId = setInterval(refresh, 5000);
}
function render(data) {
document.querySelector('#stats').textContent = JSON.stringify(data);
}
sync();
document.addEventListener('visibilitychange', sync);go deeper
Know that a background tab's timers are slowed down by the browser and that setInterval means "no sooner than", never "exactly every". Be able to say you would stop polling when the page is hidden.
Explain the mechanics: clamping to roughly a second, Chromium's minute-scale budget after a page is hidden and quiet, rAF stopping entirely, and skipped ticks being dropped rather than queued.
Demonstrate the operational fix — idempotent start/stop driven by visibilitychange plus one immediate fetch on return — and explain why fighting the throttle burns battery and floods the API for no user benefit.
Own the freshness-versus-cost tradeoff across the product: which data truly needs to arrive while a tab is hidden, whether that justifies a pushed transport and its connection cost, and what the reconcile-on-visible contract is after a gap.
## What the browser is doing and why A laptop with forty open tabs, each politely polling every five seconds, is a laptop with a hot fan and a dead battery. Browsers therefore treat timers in a hidden page as low-priority: they clamp how often the timer callback may run, independently of the interval you asked for. The broad shape, consistent across engines: background pages get their timers clamped to roughly one execution per second. Chromium goes further with an intensive policy — after a page has been hidden and quiet for several minutes, timer wake-ups are budgeted down to about one per minute. Pages doing something the user is plainly still consuming, such as playing audio, are exempted from the harshest tier. Treat the exact numbers as engine policy that shifts between releases; what you must know for an interview is the direction and magnitude: your five-second interval becomes a minute-scale interval, and that is not a bug you can file. Rendering is throttled even more aggressively than timers. `requestAnimationFrame` callbacks stop entirely in a hidden page — there are no frames to produce, so there is no reason to run frame callbacks. An animation driven by rAF simply pauses; one driven by `setInterval` limps along at the clamped rate, doing layout and state work that will never be painted. That asymmetry is a good thing to name. ## The consequences people get wrong **Ticks do not accumulate.** A throttled interval does not queue up the wake-ups it missed and fire them in a burst when the tab returns. Each clamped period simply produces at most one callback. Code that counts ticks to measure elapsed time is therefore wrong on any page that was ever backgrounded — measure with timestamps, never by counting. **Wall-clock assumptions break.** `setInterval(fn, 5000)` gives you "no more often than every five seconds", never "exactly every five seconds". If a heartbeat matters, compute the delta from `Date.now()` or `performance.now()` inside the callback and act on the delta. **Escaping the throttle is not the goal.** Candidates reach for tricks: a shorter interval to compensate, a Web Worker to run the timer, an audio element to look busy. Shorter intervals are clamped identically. Moving the timer somewhere else does not make polling that nobody can see valuable, and faking activity to defeat a battery-saving policy is user-hostile — the platform will keep tightening against it. The right answer is to stop doing the work. ## The correct shape Make visibility drive the polling, not the other way round: ```js let timerId = null; async function refresh() { /* fetch and render stats */ } function startPolling() { if (timerId === null) timerId = setInterval(refresh, 5000); } function stopPolling() { clearInterval(timerId); timerId = null; } function sync() { if (document.visibilityState === 'hidden') { stopPolling(); } else { refresh(); // immediate catch-up so the first glance is fresh startPolling(); } } sync(); document.addEventListener('visibilitychange', sync); ``` Three properties make this good. It is idempotent — calling `sync` twice does not create two intervals. It is correct at startup, including for a tab that opened in the background. And it puts a single fetch, not a queue of them, at the moment the user actually looks. ## When you genuinely need background freshness Sometimes the data must arrive while the tab is hidden — a chat message, an alert. Polling is the wrong tool for that regardless of throttling: use a connection the server can speak over, so the browser is not waking your code up on a timer at all. Push-style transports are not subject to the timer budget the same way, because the wake-up is driven by an arriving message rather than by a scheduled callback. Whatever the transport, the client still needs a reconcile-on-visible step, because a hidden page may have been frozen or evicted and missed messages entirely. ## Interview summary "Hidden tabs get their timers clamped — about a second, and roughly a minute in Chromium after a few minutes hidden — and rAF stops completely. Missed ticks are dropped, not replayed. So I stop the poll on the hidden transition and do one immediate fetch when the page becomes visible again, rather than trying to out-run the throttle."
- Does the browser replay the interval ticks it skipped once the tab becomes visible again?No. Throttling drops wake-ups rather than queueing them, so a tab hidden for ten minutes does not fire a burst of backlogged callbacks on return. Any code that counts ticks to estimate elapsed time is therefore wrong — read `performance.now()` or `Date.now()` inside the callback and work from the delta instead.
- How does requestAnimationFrame behave differently from setInterval in a hidden tab?`requestAnimationFrame` callbacks stop completely, because a hidden page produces no frames, so an rAF-driven animation self-suspends. `setInterval` keeps firing at a clamped rate, which means an interval-driven animation carries on computing state and touching the DOM for output nobody will ever see — one reason rAF is the right driver for animation.
- Can you dodge the throttle by moving the timer into a Web Worker?Treat that as a non-answer. Background throttling is a page-level policy, and even where a worker timer fires, the polling itself is still work no user can see, still costs radio and battery, and still returns data that will be stale by the time the tab is looked at. If freshness while hidden is a genuine requirement, use a server-pushed transport rather than a timer.
saying these in an interview costs you the question
- Assumes setInterval keeps exact 5-second cadence in a background tab
- Expects skipped ticks to fire in a burst on return
- Shortens the interval to compensate for throttling
- Counts interval ticks to measure elapsed time
- Thinks a Web Worker exempts the page from throttling policy