By Suraj Giri, productivity researcher at The Blog Timer. Updated 2026-05-27.

How Accurate Are Browser Timers?

Browser-based timers are accurate within roughly 10 to 50 milliseconds per minute when the tab is active, but they can drift by seconds or even minutes when the tab is backgrounded, the device sleeps, or the system is under heavy load. The accuracy depends on whether the timer is built using setInterval, requestAnimationFrame, or a timestamp-based performance.now() loop. In our research at The Blog Timer, only timestamp-based timers maintain wall-clock accuracy across tab visibility changes.

Why Do JavaScript Timers Drift?

JavaScript runs on a single-threaded event loop. When you call setInterval(callback, 1000), you are not asking the browser to invoke the callback exactly every 1,000 milliseconds — you are asking it to invoke the callback no sooner than 1,000 milliseconds from now, whenever the event loop is free. If a long-running script, a layout pass, or garbage collection blocks the loop, the next tick is delayed. Those delays accumulate.

This is why a naive setInterval-based timer drifts. If each tick is 5 ms late on average, after 12 minutes of “1-second” ticks, you have lost roughly 3.6 seconds. Over a 25-minute Pomodoro session, a busy tab can drift 5 to 10 seconds. That is unacceptable for productivity timers, where users expect wall-clock fidelity.

What is the Difference Between setInterval and Timestamp-Based Timing?

The fix is structural. Instead of counting ticks, you anchor to a timestamp:

  1. Record the start time using performance.now() (or Date.now() as a fallback).
  2. On each animation frame or interval, compute elapsed = now - start.
  3. Render remaining = duration - elapsed.

This pattern is self-correcting. If the event loop is blocked for 200 ms, the next frame still computes the correct remaining time. There is no accumulated drift, only momentary visual lag. Every accurate timer we have built — including the timers on this site — uses this pattern.

What Does performance.now() Actually Measure?

The performance.now() API returns a high-resolution timestamp in milliseconds, measured from the page’s navigation start. It has sub-millisecond resolution in modern browsers (often reduced to 1 ms or 100 µs for security reasons to prevent Spectre-class timing attacks). Crucially, it is monotonic — it never goes backward, even if the system clock changes — and it is not affected by the user adjusting their device clock or by daylight-saving transitions. It is the right primitive for measuring elapsed time.

Date.now(), by contrast, returns wall-clock time and can jump forward or backward if the system clock is corrected. Use it as a fallback, never as a primary clock for elapsed-time calculations.

How Does Background Tab Throttling Affect Timers?

Every modern browser throttles inactive tabs to save battery and CPU. Chrome, Firefox, Safari, and Edge each implement variants of the policy: when a tab is hidden, setInterval and setTimeout callbacks fire at most once per second, and after about five minutes of inactivity they may be throttled to once per minute or paused entirely. requestAnimationFrame is suspended completely in background tabs.

This means a timer that visually counts down using requestAnimationFrame will appear frozen if the user switches tabs — but if it is correctly anchored to performance.now(), the moment the tab becomes visible again, it recomputes elapsed time and snaps to the correct remaining duration. The visual is delayed, but the underlying time is not.

What is the Page Visibility API?

The Page Visibility API tells you whether the page is currently visible. Listening to the visibilitychange event lets a well-built timer:

  • Pause expensive rendering when hidden.
  • Recompute remaining time when shown again.
  • Trigger a sound or notification at the correct moment, even if the visual countdown was suspended.

Combining Page Visibility with a timestamp anchor and a server-time check (for very long timers) produces a timer that survives tab switching, lock-screen events, and brief sleep cycles.

How Does requestAnimationFrame Compare?

requestAnimationFrame is the right primitive for the visual update loop. It runs synchronously with the display refresh (typically 60 Hz, sometimes 120 Hz on high-refresh devices), which means animations appear smooth. But it is not a clock — it does not run in background tabs, it can skip frames when the system is busy, and the time between frames is not guaranteed.

The right architecture: use requestAnimationFrame to schedule visual updates, and use performance.now() inside each frame to compute remaining time. This decouples display smoothness from timing accuracy.

What Happens to Timers on Mobile?

iOS Safari

iOS aggressively suspends background tabs and locks them when the screen turns off. A timer built only with JavaScript intervals will stop running when the user locks the phone. To survive screen lock, the timer must either (a) recompute elapsed time on resume using a stored start timestamp, or (b) use a service worker with the Notifications API to schedule an alarm. iOS also tightens timer resolution under low-power mode.

Android Chrome

Android allows somewhat more background CPU than iOS, but recent Chrome versions implement intensive throttling: after a tab has been hidden for five minutes, timers fire at most once per minute. Service workers and Web Push notifications are the reliable path for scheduled alerts. The same timestamp-anchor pattern keeps visual timers accurate across resume.

The Math of Drift Accumulation

Suppose a naive setInterval timer with a 1,000-ms interval is delayed by an average of δ milliseconds per tick. After N ticks, the accumulated drift is approximately N × δ. With δ = 8 ms, a 25-minute (1,500-tick) timer drifts about 12 seconds. With δ = 25 ms under load, the same timer drifts more than 35 seconds. Over a 60-minute session, the drift can exceed a full minute — enough to invalidate the timer entirely.

Timestamp-anchored timers reduce the drift to whatever resolution performance.now() provides (usually 1 ms or better) plus the rendering latency of the current frame (16.7 ms at 60 Hz). The drift is bounded, not cumulative.

How Do We Test Timer Drift at The Blog Timer?

Our internal testing protocol is straightforward but rigorous:

  1. Cold-start test. Open a fresh tab, start a 25-minute timer, and let it run with the tab visible. Compare the final reading against a reference atomic-clock service.
  2. Background-tab test. Start the timer, switch to a different tab for 20 minutes, return, and verify the displayed remaining time.
  3. Load test. Run the timer while a CPU-intensive script (image processing, video transcoding) runs in the same tab. Measure drift.
  4. Mobile sleep test. Start the timer on iOS and Android, lock the screen for 10 minutes, unlock, and check remaining time.
  5. Cross-device wall-clock test. Run the same duration on multiple devices simultaneously, all started within one second of each other. The finish times should agree within two seconds.

A correctly implemented timestamp-anchored timer passes all five tests with drift under one second.

Native vs. Browser vs. App Timers: A Comparison

Timer Type Accuracy (foreground) Accuracy (background) Survives sleep Notifications
Browser (timestamp-anchored) ±16 ms Recomputed on resume Yes (on resume) Limited (tab must be open)
Browser (naive setInterval) ±50 ms Throttled, drifts No None
PWA with service worker ±16 ms Scheduled alarms Yes Push notifications
Native mobile app ±1 ms OS-level alarms Yes Local notifications
Physical kitchen timer ±1-2 s/hour N/A Yes Mechanical bell
Smartwatch timer ±10 ms OS-level Yes Haptic + visual

Why a Mechanical Timer Sometimes Wins

A wind-up kitchen timer is dramatically less accurate than a digital clock — typical mechanical timers drift by 5-10% — but it has properties no software timer can match. It is glanceable from across the room, it does not steal attention by sitting on a screen full of notifications, and it cannot be silenced by a stray tap. For productivity work, the right timer is the one that does not betray the user. For some users that is a browser-based Pomodoro timer; for others it is a $4 plastic tomato.

The Standards That Govern Time on the Web

Browser timers ultimately reduce to the system clock, which is itself synchronized using Network Time Protocol (NTP). NTP keeps consumer devices within about 10 ms of Coordinated Universal Time (UTC) under normal network conditions. For any timer that needs to be accurate across devices, sync to wall-clock UTC at start and compute everything from there.

Why Some Browser Timers Are Off By Whole Minutes

When a user reports that a browser timer is “off by a minute,” the cause is almost always one of three things: the timer is naive setInterval-based and the user backgrounded the tab; the device entered a low-power state and the JavaScript runtime was suspended; or the page-visibility recompute step was missing from the implementation. None of these are bugs in JavaScript — they are bugs in how the timer was built.

A well-built browser timer has three layers:

  1. The time source. performance.now() captured at start, used for every elapsed-time computation.
  2. The visual loop. requestAnimationFrame for smooth countdown rendering while the tab is visible.
  3. The resume hook. document.visibilitychange listener that recomputes elapsed time the instant the tab becomes visible again.

Optionally, a fourth layer registers a service worker with a scheduled notification, so the alert fires even if the tab is closed or the device is asleep. Without that fourth layer, the timer is accurate but cannot notify in the background.

The Hidden Cost of Timer Drift

For productivity timers, drift is more than a cosmetic annoyance. A Pomodoro user who runs eight pomodoros per day with a 5% drift loses or gains roughly 60 seconds across the day. Compounded across a week of unsynchronized devices (browser timer at the desk, phone timer in the kitchen), users develop the (correct) impression that their tools cannot be trusted. Trust in a productivity tool is itself part of what makes the tool effective; a drifting timer corrodes that trust.

How We Tested Our Timers

Before shipping our public timers, we ran the five-test protocol described above on every supported platform combination: Chrome on macOS, Safari on macOS, Chrome on Windows, Edge on Windows, Safari on iOS, Chrome on iOS, Chrome on Android, and Firefox on each desktop platform. The acceptable drift threshold is one second on a 25-minute timer for every combination. The implementation that passes uses the timestamp-anchor pattern with the page-visibility recompute hook; no naive setInterval timer passes.

Frequently Asked Questions

Why does my browser timer slow down when I switch tabs?

Background tabs are throttled by every major browser to save energy. The fix is a timestamp-anchored timer that recomputes remaining time on visibility change rather than relying on continuous tick counting.

Is performance.now() the same on every browser?

The API is universal, but resolution varies. Chrome and Edge return 100-microsecond resolution by default; Firefox returns 1-millisecond resolution; Safari intentionally adds jitter. For productivity timers, the differences are irrelevant — millisecond resolution is more than enough.

Can a JavaScript timer survive my phone going to sleep?

Not while it is asleep — JavaScript does not run when the device is suspended. But a correctly built timer recomputes the remaining time the moment the device wakes and the page becomes visible again. To receive an alert while the device is asleep, you need a service worker with notification permission, or a native app.

What is the most accurate browser timer pattern?

Anchor to performance.now() at start, recompute remaining time inside a requestAnimationFrame loop, listen to visibilitychange for recomputation on resume, and schedule a service-worker notification for the finish moment.

Do smartwatch timers drift?

Modern smartwatches use the same OS-level timing primitives as their parent phones and are accurate to within tens of milliseconds. The bigger source of perceived error is the haptic delivery, not the underlying timer.

How do I test my own timer for drift?

Run it next to time.is or another atomic-clock-synchronized reference for a 25-minute or longer block, and compare the finish moments. Repeat the test with the tab backgrounded for half the run.

Why is my online timer sometimes a full second off?

One-second offsets usually come from rendering latency, not timing logic. The underlying time is correct, but the visual update happens on the next animation frame. For a productivity timer, this is invisible.

Browse Related Guide Topics

Frequently Asked Questions

Yes. This timer uses your device's internal clock and tracks the end timestamp, not individual ticks. This means it stays accurate even if your browser tab goes to sleep or your device briefly lags.

Absolutely. This timer works on any device with a modern web browser—phones, tablets, laptops, and desktops. No app download required.

See Also