Back to articles

2 min read ArchitectureStateProduct

Where a timer should live

A countdown in the browser is wrong the moment a tab sleeps. Storing the session as one absolute moment on the server ends a whole category of bugs.


The first version of any timer counts down in the browser. It works on your machine, in your tab, while you are watching it. Then it meets the real world.

A tab in the background gets throttled. A phone locks and the page is frozen. Somebody refreshes. Somebody opens the same app on a laptop and a phone and sees two different numbers. Each of these is reported as a separate bug, and each gets its own patch, and none of the patches are the fix.

The fix is deciding who owns the truth

Store the session as one absolute moment: it started at this instant and it runs for this long. Put that in the database. Every client that wants to know how much time is left computes it from the current time, and every client gets the same answer.

The browser stops owning time and starts rendering it. A refresh recomputes. A frozen tab that wakes up recomputes. A second device recomputes. The bugs do not get fixed one at a time, they stop being possible.

What this makes cheap

Once the server owns the session, several features that were awkward become almost free.

  • Continuity across devices. Start on a laptop, finish on a phone. There is nothing to synchronise because there was never a second copy.
  • An honest record. The session already exists as a row with a start and a length. History is a query, not a separate logging system you have to keep in step.
  • Trustworthy clients. A native application does not need to reimplement the timer, only to read it. Adding a platform means implementing a contract that already exists.

What it costs

Two things, and they are worth naming.

The first is a network dependency. The client needs one round trip to learn what is running. Handle it explicitly: show a loading state that means loading, and a failure state that means failure, rather than defaulting to zero and pretending nothing is running.

The second is clock skew. A client with a wrong system clock computes a wrong remaining time. Send the server’s current time along with the session so the client can measure the offset once and apply it, rather than trusting the device.

The general shape

This is not really about timers. It applies to anything where the answer must be the same in two places at once: a countdown, a booking hold, a rate limit, a lock, a queue position.

Pick one place to hold the truth. Everywhere else renders it. Most synchronisation code exists because that decision was never made.

Have something you want built?

Write a few lines about what you need and roughly when. You get a straight answer about whether we are the right fit, and what it would take.