Guide

Why countdown timers drift, and how to keep one honest

Leave a countdown open in a browser tab for a day and come back to it. Quite often the number is wrong, sometimes by minutes. The clock on the same screen is fine, so it is not the machine. It is how the countdown was written.

The two ways to build a countdown

  • Count your own ticks. Start from a total and subtract one second every time a timer fires. Simple, and wrong over time.
  • Recompute from the target. Every time you repaint, take the difference between the target instant and the current instant. The display is derived, not accumulated.

The second approach is barely more code and cannot drift, because nothing is being accumulated to drift.

Why the first one drifts

A browser timer is a request, not a guarantee. It fires at least after the interval you asked for, and often later: the page may be busy, the tab may be in the background where timers are deliberately throttled, or the device may have been asleep. Every late tick is a fraction of a second lost, and losses only accumulate in one direction. Over hours it becomes visible, and a laptop that was suspended overnight can come back with a countdown that is hours out.

The other sources of error

  • The visitor's clock is wrong. A countdown computed against the local device clock inherits whatever error it has. Fetching a reference time from a server once on load, and computing the offset from it, protects against this.
  • The target was set in local time. Then it is not the same target for everyone, which is a different problem with the same symptom of people disagreeing about the number.
  • Clock changes. If the remaining time is computed by subtracting local readings rather than instants, it will jump by an hour across a daylight saving change.

What a well-behaved countdown does

  • Stores the target as an instant, in UTC.
  • Recomputes the remainder from the current instant on every repaint.
  • Recovers correctly after the tab is hidden or the device sleeps.
  • Shows the target's local reading as well as the remaining time.
  • Does something sensible at zero rather than counting into negative numbers.

The countdown timer works this way, which is why leaving it open and coming back to it later gives the same answer as reloading the page. If you are building your own, the recompute-from-target rule is the one thing worth copying.