alarm-clock.org/methodology/browser-timing

How accurate is a browser timer?

The timer, stopwatch and alarm clock here all run on browser timers. Rather than describe how those behave, this page measures it — and is equally specific about what it could not measure.

The setup

EngineChromium 141.0.7390.37
ModeHeadless, no GPU
PlatformLinux x86_64
Methodseo/tools/measure-timing.mjs, run with Playwright

This is one engine on one machine. It is not a cross-browser comparison: Firefox and WebKit could not be installed in the environment these were taken in, so there are no Firefox or Safari figures here, and there are no estimated ones either. Read every number below as "Chromium, on a quiet Linux box", because that is exactly what it is.

A one-second tick, undisturbed

Sixty seconds of setInterval(fn, 1000) in a foreground tab, with each tick compared against where it was scheduled to land.

Ticks expected / received60 / 60
Typical error per tick1 ms
Worst error per tick1 ms
Accumulated drift over the minute−1 ms

The interesting result is the last row. A common worry is that setInterval compounds its own lateness — that a tick two milliseconds late makes the next one two milliseconds late as well, forever. It does not: the browser schedules against the original start, so lateness is corrected rather than carried. Over a minute the total came to −1 ms — a millisecond early, not a second late.

The same tick on a busy machine

The condition people actually complain about is not an idle laptop — it is one compiling something in the next window. These runs throttle the CPU by a fixed factor: 4× is roughly a mid-range phone, 20× is a machine in trouble.

4× slowdown — worst tick error2 ms
4× slowdown — ticks expected / received20 / 20
20× slowdown — worst tick error10 ms
20× slowdown — ticks expected / received20 / 20
20× slowdown — drift, extrapolated to an hour1.6 s

No ticks were dropped even at 20×; they arrived late, not never. The extrapolated hourly figure is what a naive counter — one that adds a second per tick — would be out by after an hour of that treatment. It is also entirely avoidable, which is the next section.

Why the displayed time does not drift at all

There are two ways to write a countdown. Count the ticks and multiply, or store the target instant and recompute what is left on every tick. The first is simpler and is wrong by exactly the drift measured above. The second is what everything on this site does.

The same runs measured both. Against a clock read fresh at the moment of comparison, the recomputed value was never out by more than 1 ms — in the undisturbed run, under 4× load, and under 20× load alike. A late tick makes the display update late; it does not make it show the wrong number.

This is why a timer left running for an hour finishes when it should, and why the same design is used for the alarm clock and every countdown page.

The four-millisecond floor

A chain of setTimeout(fn, 0) calls does not run instantly. The specification permits a floor once the chain is five deep, and here it settled at about 4.2 ms per step.

Measured gaps, in milliseconds, from the first nested call onward: 0 · 0 · 0 · 0.1 · 0 · 0 · 4.2 · 4.2 · 4.2 · 4.2 · 4.2

Nothing here counts elapsed time by chaining timeouts, for this reason.

What could not be measured, and why it is absent

Three behaviours matter a great deal for an alarm clock and are not quantified on this page. They are missing because the environment could not produce them honestly — not because they are unimportant, and there are no estimates standing in for them.

Hidden-tab throttling (one timer wake per second, then one per minute after five minutes). Headless Chromium has no window manager, so a page is never actually hidden and the throttling policy never engages. CDP has no visibility override.

requestAnimationFrame stopping while a tab is hidden. Same reason — the page cannot be made hidden.

A frozen background tab. CDP accepts the freeze but it does not engage: the page still ran JavaScript while supposedly frozen, which a frozen page cannot do. Chromium only freezes a page that is already hidden.

Autoplay policy (whether audio can start without a user gesture). Playwright launches Chromium with --autoplay-policy=no-user-gesture-required, so this would measure the harness, not the browser.

One of these was written, run, and thrown out. A page-freeze test appeared to work — the browser accepted the command without error — but the page kept ticking at a steady one per second throughout, and still ran JavaScript on request, which a genuinely frozen page cannot do. The freeze had not engaged. Publishing those ten ticks as "a frozen tab keeps counting" would have been a measurement of nothing, presented as a fact about browsers. The test that remains in the script now asserts the freeze fails, so nobody adds the numbers back by accident.

For what these behaviours mean in practice, in plain language: browser limitations.

Related

Browser limitations, in plain language · Online timer · Online stopwatch · Classroom timer & stopwatch guide

💡 How do we make alarm-clock.org better? Share an idea →

Every tool here is free, needs no sign-up, and gets better because people tell us what's missing. If you have an idea, we'd love to hear it.

Every suggestion is read and reviewed. We can't promise to act on them all, but they directly shape what we build next.