Online Timer: How to Run an Accurate Countdown (2026)
Why a browser online timer drifts, how throttling in background tabs slows it down, and how to run a countdown that stays accurate to the second.
Online Timer: How to Run an Accurate Countdown in Your Browser
You open an online timer, set five minutes, switch to another tab, and come back to find it showing 4:47 when your phone says the five minutes are up. The timer isn't broken — the browser deliberately slowed the page down while you weren't looking. This post explains what actually happens to a countdown in a background tab, why setInterval drifts, and how to tell an accurate timer from a sloppy one.
TL;DR
- Browsers throttle timers in hidden tabs to save battery — by design.
setIntervalaccumulates error; deadline-based scheduling does not.- Store an end timestamp, then compare against
performance.now(). - A hidden tab's clock is still right; only the repaint is late.
- Closing the tab kills the timer — minimising it does not.
How to start an online timer in your browser
The whole appeal of a browser timer is that there's nothing to install. Open a URL, set a duration, hit start. No app store, no account, no notification permissions unless you want the alert.
Counting down from a fixed duration
A countdown is the common case: 5 minutes for tea, 25 for a Pomodoro block, 90 seconds for a plank. You enter the duration, the page stores an end time, and it redraws the remaining time until it hits zero. The iKit Stopwatch & Timer runs the whole thing client-side — the duration never leaves your machine, and there's no server round-trip to introduce lag.
Counting up with a stopwatch
A stopwatch is the inverse: start at zero, count up, record laps. Use it when you don't know how long something will take and you want the number afterwards — debugging a slow build, timing a run, measuring how long a meeting overran.
Timer vs stopwatch vs countdown-to-a-date
These three get conflated constantly, and picking the wrong one is why people end up fighting their tool.
| Tool | Direction | Best for |
|---|---|---|
| Timer | Down from a duration | Focus blocks, cooking, workouts |
| Stopwatch | Up from zero | Measuring actual elapsed time |
| Date countdown | Down to a calendar date | Launches, exams, events |
If you're counting down to a specific calendar moment rather than a duration — a product launch, a deadline, New Year — a countdown timer is the right shape, because it anchors to a date rather than to "now plus N minutes."
Why does my browser timer slow down in a background tab
This is the single most-reported "bug" in every web timer ever shipped, and it isn't a bug. Browsers aggressively throttle JavaScript timers in hidden pages because a few dozen background tabs each waking the CPU every 250 ms is a measurable chunk of a laptop battery.
Chrome's three throttling tiers
Chrome's behaviour is documented in detail in the Chrome team's write-up on heavy throttling of chained JS timers, shipped in Chrome 88 back in January 2021 and still the model today. Timers fall into one of three buckets:
- Minimal throttling — page visible, or made a sound in the last 30 seconds. Timers run as requested, with the long-standing 4 ms floor after five nested calls.
- Throttling — page hidden for under five minutes. Timers are checked once per second and batched together.
- Intensive throttling — page hidden more than five minutes, silent for at least 30 seconds, chain count of five or more. Timers are checked once per minute.
That last tier is why a timer left in a background tab for ten minutes can look wildly wrong the instant you switch back to it.
Firefox and Safari use budgets
Firefox and Safari get to the same place by a different route. Per MDN's Page Visibility API documentation, background windows are given a time budget — Firefox measures it in milliseconds, Chrome in seconds — that regenerates at roughly 10 ms per second. Run out of budget, and your timer tasks stop being scheduled until it refills. Tabs playing audio or holding a live WebSocket or WebRTC connection are exempt.
Why a good timer is still correct afterwards
Here's the part that matters: throttling delays when your code runs, not what time it is. If the timer stored an end timestamp at start, then the moment it does get a chance to run — even a full minute late — it subtracts the current clock reading from that stored deadline and gets the right answer immediately. The display was stale; the timer wasn't wrong.
A timer built the naive way has no such recovery, which brings us to the actual failure mode.
Why setInterval drifts and performance.now() does not
Almost every broken web timer is broken the same way: it treats setInterval(fn, 1000) as though it fires exactly once per second and decrements a counter.
The accumulating-error version
let remaining = 300; // 5 minutes
setInterval(() => {
remaining -= 1;
render(remaining);
}, 1000);
This is wrong in a way that compounds. MDN's setInterval() reference is explicit that the delay is a minimum, not a guarantee — the callback is queued after the interval and runs whenever the event loop is free. Every late callback here is a second permanently lost, because the counter only ever moves in whole steps. Ten milliseconds of jitter per tick is 3 seconds of error over an hour. A throttled background tab turns that into minutes.
The deadline version
Store the end, measure against a clock, derive the remaining time every frame:
function startTimer(durationMs, onTick, onDone) {
const end = performance.now() + durationMs;
function frame() {
const left = end - performance.now();
if (left <= 0) return onDone();
onTick(left);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
}
Nothing accumulates. A late frame produces a slightly stale reading for one frame and then self-corrects. requestAnimationFrame is also the right scheduler for a visual countdown: it syncs to the display refresh rate and stops entirely in hidden tabs, so you burn no CPU while backgrounded.
Date.now() vs performance.now()
Both are millisecond clocks, and they fail differently.
Date.now() |
performance.now() |
|
|---|---|---|
| Origin | Unix epoch | Page/worker start |
| Resolution | 1 ms | Sub-ms, coarsened |
| Wall-clock jumps | Affected | Immune |
| Ticks during OS sleep | Yes | Windows only |
performance.now() is monotonic — it never decreases and isn't touched by NTP corrections, daylight-saving shifts, or the user dragging the system clock, which makes it the correct choice for measuring a duration. The catch is documented on MDN's performance.now() page: the spec requires it to keep ticking through OS sleep, but in practice only browsers on Windows do. Chrome, Firefox and Safari all have open bugs for this on other platforms.
For a long timer that might outlive a laptop lid closing, belt-and-braces is to keep both readings and reconcile on wake:
const wall = Date.now() + durationMs;
document.addEventListener("visibilitychange", () => {
if (!document.hidden) render(wall - Date.now());
});
If you want the epoch value behind Date.now() in a readable form while debugging, the Unix timestamp converter will turn those 13-digit numbers into dates without you doing arithmetic in your head.
Where the 4 ms floor comes from
The nesting rule people repeat as folklore is actually normative. The WHATWG HTML Standard's timers section defines a timer nesting level, and once a chain of setTimeout calls — or a repeating setInterval — passes five levels, the interval is clamped to at least four milliseconds. It applies in every browser, and it's why "one-millisecond timers" don't exist on the web.
Is an online timer accurate enough for real work?
Depends entirely on what "accurate" means for your task. Here's the honest breakdown.
Accuracy by use case
| Use case | Tolerance | Browser timer? |
|---|---|---|
| Pomodoro, cooking, meetings | ±1–2 s | Yes, comfortably |
| Workout intervals, HIIT | ±0.5 s | Yes, keep tab visible |
| Presentations, exams | ±1 s | Yes |
| Race timing, lab work | ±10 ms | No — use hardware |
For anything in the top three rows, a deadline-based browser timer is more accurate than you need. The limiting factor isn't the clock, it's your reaction time hitting start.
When a browser timer is the wrong tool
Two cases. First, if the timer must survive the tab closing — a browser timer lives in the page, and closing it ends the countdown. Minimising, switching tabs, or locking the screen is fine; closing is not. Second, if you need certified timing for competition or measurement, where the sub-millisecond precision of performance.now() is irrelevant next to the uncertainty of a browser's audio and repaint pipeline.
Why the alert may fire late
Even a perfectly accurate timer can sound late. The countdown hits zero on schedule, but if the tab is throttled the code that triggers the beep may not run for up to a minute. Timers that play a silent audio track to stay in Chrome's "minimal throttling" tier get around this, at the cost of holding the audio pipeline open. It's a trade-off, not a fix, and it's worth knowing which one your timer made.
Online timer vs phone timer vs kitchen timer
A browser timer isn't strictly better than the alternatives — it's better at specific things.
- Big, visible, shareable. A fullscreen browser countdown on a projector works for a room. A phone timer doesn't.
- No context switch. You're already in the browser. Reaching for a phone is how twenty minutes disappear.
- Multiple at once. Several tabs, several timers, all labelled and visible.
- Nothing installed, nothing tracked. A client-side timer sends nothing anywhere.
Where phones win: they survive you closing the laptop, and their alarms cut through Do Not Disturb. Where a physical kitchen timer wins: it doesn't need a screen at all. Most people end up using a browser timer for desk work and a phone alarm for anything they genuinely cannot miss — which is the correct answer, not a compromise.
The privacy angle is worth a line too. A timer is about the least sensitive thing you can put in a web page, but the labels aren't: "call with legal", "interview — candidate 3". Every tool in the iKit suite runs entirely in your browser, so those labels stay on your machine.
References
- Heavy throttling of chained JS timers beginning in Chrome 88 — Chrome's three throttling tiers, the five-minute and 30-second thresholds, and the chain-count rule.
- Window: setInterval() method — MDN — confirmation that the delay argument is a minimum, and the reasons for longer delays than specified.
- Performance: now() method — MDN — monotonic-clock semantics, resolution coarsening, and the OS-sleep behaviour differences across platforms.
- Page Visibility API — MDN — background timeout budgets in Firefox and Chrome, and which processes are exempt from throttling.
- HTML Standard — 8.7 Timers — normative definition of timer nesting level and the four-millisecond clamp.
Related on iKit
- Epoch time units cheat sheet: seconds, millis, micros, nanos — the unit confusion behind most timing bugs, including the 1000× errors that show up when mixing
Date.now()with a backend clock. - Why your JavaScript timestamp is 1000× bigger than your backend's — the exact millisecond-vs-second mismatch that turns a five-minute timer into an 83-hour one.
- Convert a date to a Unix timestamp in Bash, Python, JS and SQL — how to produce the end-timestamp value a deadline-based timer needs, in whichever language you're scripting in.
- Convert a Unix timestamp to a date without timezone bugs — what happens when a stored deadline crosses a DST boundary, and why monotonic clocks sidestep the problem.
Related posts
Tailwind Color Palette Looks Uneven? Fix It With OKLCH (2026)
Your custom Tailwind color palette looks uneven because sRGB tints drift in hue and lightness. Here is how to rebuild the 50–950 ramp in OKLCH.
CSV to JSON: The Complete 2026 Guide for Developers
How CSV to JSON conversion really works in 2026 — RFC 4180 quoting rules, delimiter detection, BOM and encoding traps, and safe type inference.
OKLCH Color Ramp: Build a Perceptually Uniform Scale (2026)
Build an OKLCH color ramp where every step looks evenly spaced. A practical 2026 guide to lightness stepping, chroma tapering, and gamut safety.