Start a 25 minute countdown in a browser tab, switch to another tab, come back half an hour later, and many online timers still show minutes left. Nothing is broken in your computer: browsers deliberately slow down pages you are not looking at, and a timer built the naive way turns that slowdown into lost time. This article explains what browsers actually do, works through the numbers, and shows why a stopwatch is easy to get right while a countdown alarm is harder. The stopwatch and countdown timer and the Pomodoro timer on this site are built the way described at the end.
What browsers do to hidden tabs
JavaScript timers (setTimeout and setInterval) never promised to be exact. MDN's documentation lists the reasons a timer fires later than asked: once timers are nested five levels deep the browser enforces a minimum of 4 milliseconds; a callback cannot run until the code currently running has finished; and to save battery, browsers enforce a minimum delay in inactive tabs. The details differ by browser. MDN gives Firefox's figures: desktop Firefox uses a minimum of one second for inactive tabs, and Firefox for Android a minimum of 15 minutes, and it may unload such tabs entirely.
Chrome documented its rules when version 88 shipped in January 2021. In a hidden tab, timers are normally checked once per second. On top of that comes "intensive throttling", where timers are checked only once per minute. It applies when all of these are true: the page has been hidden for more than five minutes, the timer is part of a chain of five or more (a setTimeout that schedules the next setTimeout, or a setInterval, where every run counts as part of the chain), the page has been silent for at least 30 seconds, and WebRTC is not in use. A ticking clock on a web page meets every one of those conditions after five quiet minutes in the background.
requestAnimationFrame, the usual way to draw smooth animations, goes further: it waits until the page is visible again, so in a hidden tab it does not run at all.
The arithmetic of a drifting countdown
The naive countdown keeps a number of seconds and subtracts one each time a once-a-second timer fires. It assumes every tick is exactly one second apart. Here is what that assumption costs.
Visible tab, small delays. Suppose each tick, scheduled with setTimeout from the previous one, runs 4 ms late. The counter reaches zero after 1,500 ticks, but 1,500 ticks of 1.004 seconds take 1,506 seconds: the 25 minute alarm rings 6 seconds late. Small, but it grows with the length of the timer and with how busy the page is.
Hidden tab, Chrome's intensive throttling. Start the same 25 minute countdown and switch tabs right away. For the first five minutes, ticks still arrive about once a second, so the counter has subtracted about 300 seconds. After that, the chained timer runs about once a minute, and each run subtracts one second. When 25 real minutes have passed, the counter has subtracted roughly 300 + 20 = 320 seconds and shows about 19:40 left. At one subtracted second per minute, the remaining 1,180 seconds would take about 20 hours to count down. The exact figures depend on the browser version and the page, but the direction is always the same: counting ticks loses time whenever ticks are late.
Stopwatch vs countdown
The fix for the counting is the same for both: do not count ticks, store a timestamp.
- A stopwatch stores the moment it started. Elapsed time is now minus start, plus whatever earlier runs added. Every time the page draws, it reads the clock and computes the answer, so a late or skipped tick only delays the display. Come back after an hour in the background and the first frame shows the right time. Nothing has to happen while you are away, so a stopwatch is easy to get right.
- A countdown stores the moment it will end. Time left is end minus now, which is correct whenever it is computed. But a countdown has a second job: it must do something at the end, such as ring an alarm or show a notification, while you are not looking. That needs the page to wake up at the right moment, which is exactly what throttling prevents.
So a countdown needs extra care for the alarm itself. Two techniques help. First, schedule one timeout for the exact end time instead of relying on the repeating display tick. A single timeout is not part of a chain, so Chrome's once-a-minute rule does not apply to it; it falls under the ordinary once-per-second check for hidden tabs. Second, schedule the sound in advance on the Web Audio clock. Audio is rendered on its own thread, and a sound scheduled to start at a given audio time plays then, even if the page's JavaScript timers are throttled. Browsers only let a page start audio after a click, so the start button is the moment to set this up.
Which clock to read
Date.now() returns the wall-clock time; performance.now() returns a high-resolution time from a monotonic clock. MDN points out the trade-off: Date.now() can be affected by system and user clock adjustments, while performance.now() never decreases and is not subject to adjustments. For short measurements such as benchmarks, performance.now() is the better choice. For timers that may run across a laptop going to sleep, MDN also notes a catch: the specification asks performance.now() to keep ticking during sleep, but in practice it appears that only browsers on Windows do. A countdown that must end at a real time after the lid has been closed is therefore better served by Date.now(). The timers on this site use Date.now(); the cost is that if the computer's clock is corrected while a timer runs, the reading jumps by that correction.
Common mistakes
- Counting ticks. Any counter of setInterval callbacks drifts, visible or not. Store a start or end timestamp and compute from it.
- Animating with requestAnimationFrame alone. It stops in hidden tabs, so anything driven only by it (including an alarm check) stops too. Use it for drawing, not for deciding when time is up.
- Expecting the tab title to tick every second. A title countdown is updated by a timer, so in a long-hidden tab it may change only once a minute. The time it shows is still right when it does update.
- Relying on sound without a click. Browsers block audio that starts without a user gesture. Test the alarm once, and check that the tab is not muted.
- Closing the tab or letting the computer sleep. A web page cannot ring after it is closed, and a sleeping computer plays nothing until it wakes.
- Trusting hundredths of a second. A stopwatch can display them, but your reaction to the start and stop is usually the larger error.
Summary
Browsers slow down hidden tabs on purpose: about once per second in general, once per minute in Chrome after five quiet minutes, and not at all for animation frames. A timer that counts ticks loses time; a timer that stores timestamps never does. The stopwatch only needs a timestamp. The countdown also needs a reliable way to wake up for the alarm: a single exact timeout and a sound scheduled on the audio clock. Try both in the stopwatch and countdown timer.
Sources: "Heavy throttling of chained JS timers beginning in Chrome 88", Chrome for Developers blog (developer.chrome.com/blog/timer-throttling-in-chrome-88); MDN Web Docs, "Window: setTimeout() method", section "Reasons for delays longer than specified"; MDN Web Docs, "Performance: now() method".