A live clock ticking down to three decimal places looks authoritative. The digits move fast enough to feel like a precision instrument. They are not. The number on screen is a sample, taken from a timestamp that was already stale when the frame was drawn, and it is only as trustworthy as the operating system clock behind it.

A browser can read time with sub-millisecond resolution. It can show it only as fast as the screen refreshes. It can mean it only as accurately as the OS clock allows. Three separate limits, three separate clocks.

What a three-decimal clock actually shows

A digital readout flashing 14:23:45.123 does not claim the time is exactly 123 thousandths past the second. It claims the browser's clock said so at the moment the page last repainted. That moment could be 5 thousandths ago or 50 thousandths ago, depending on the screen's refresh rate and how hard the browser is working.

The page is not lying. It is showing a timestamp that was already old when it was drawn.

Resolution is how finely a value is expressed. Accuracy is how close that value sits to true time. Latency is the gap between the browser learning the time and the pixel changing on screen. A page can have perfect resolution and still be a frame behind. A frame at 60Hz lasts 16.67 thousandths of a second. At 144Hz it is still 6.94 thousandths. The visual update can never outrun the screen.

The two clocks inside every browser

Date.now(): wall-clock time for showing

Date.now() returns the number of thousandths since 1 January 1970 UTC, the Unix epoch. It answers "what time is it?" It follows the system clock, so it reflects every correction, manual change, or time zone adjustment the operating system applies. It can move backwards. It can jump forward. It can be wrong.

performance.now(): monotonic time for measuring

performance.now() returns a fractional number of thousandths since the page's own time origin, roughly when the page started loading. It answers "how long did that take?" It is monotonic: it only ever increases, and it is unaffected by system clock changes.

Why mixing them breaks stopwatches

A stopwatch built on Date.now() can show negative elapsed time if the system clock is stepped backwards by NTP. A stopwatch built on performance.now() cannot, because the value is constructed to never decrease. Date.now() is for showing the time of day. performance.now() is for measuring a span. Each has its job. Neither can do the other's.

One more detail: browsers intentionally reduce the resolution of both clocks as a defence against timing side-channel attacks. performance.now() is rounded to about5 microseconds in most browsers, and Date.now() to about1 thousandth. Neither reports the hardware's finest granularity.

What a DOMHighResTimeStamp actually delivers

When requestAnimationFrame fires, the callback receives a DOMHighResTimeStamp. That is a high-resolution, monotonic timestamp with sub-thousandth precision. It belongs to the same clock family as performance.now() and carries the same guarantee: it never goes backwards.

The timestamp tells you how much time has elapsed since the page's time origin, measured in fractions of a thousandth. It is not wall-clock time. You cannot read the time of day from it directly. But you can measure the gap between two frames with far more precision than Date.now() allows.

The trade-off: a monotonic reading is meaningless as a time of day. A page that wants sub-second precision on both questions keeps both clocks. Date.now() to show the time. performance.now() to measure durations. Neither can do the other's job.

Why the pixel you see is already 16 to50 thousandths old

Here is what happens when a page wants to show a ticking clock with fractional seconds:

  1. The page asks the browser for the current time.
  2. The browser asks the operating system.
  3. The page formats that value into text.
  4. The text is drawn into a frame.
  5. The frame is handed to the compositor.
  6. The compositor waits for the next vertical blank, the moment the screen finishes drawing the previous frame.
  7. The screen lights up the new pixels.

The vertical blank bottleneck

Each step takes time. The big one is step6. If your screen runs at60Hz, a new frame can appear only every16.67 thousandths. If the page finishes its work just after that window closes, it waits for the next one. The timestamp you see is typically somewhere between8 and50 thousandths old by the time it is physically visible.

Why this is not a bug

This is the rendering pipeline working exactly as designed. But it means a clock showing 14:23:45.123 may actually represent 14:23:45.098 in real time. The page cannot show you "now." It can only show you what "now" looked like a frame ago.

How60Hz and144Hz screens change what you perceive

The refresh rate of your display sets the upper limit on how often the page can update what you see. A60Hz display redraws60 times per second. A144Hz display redraws144 times per second. The faster the refresh, the more frequently the clock can tick, and the more responsive the fractional seconds feel.

The sampling problem

A sub-second readout on a60Hz screen shows the same hundredth for16.67 thousandths. On a144Hz screen, it changes every6.94 thousandths. Neither is actually showing every thousandth. They are showing samples of the thousandth stream, and the sampling rate is the refresh rate.

What higher refresh actually improves

Higher refresh rates make the motion feel smoother and reduce the average age of the timestamp you see. They do not make the browser's clock more accurate. They reduce the latency between the clock reading and the pixel update. The difference between60Hz and144Hz is about10 thousandths of visual lag. Small for a clock. Significant for a stopwatch.

When does browser millisecond precision actually help?

Sub-second precision is not useless. It is useful in specific situations where the exact value matters more than wall-clock accuracy.

Experiment and lab timing

Timing experiments where you need to see whether something happened within the same tenth of a second. Not for calibrated measurement, but for casual observation where a one-second tick is too coarse.

Stopwatches and countdowns

Tools where the monotonic clock measures elapsed time and the visual readout is for reference. The stopwatch logic runs on performance.now(). The screen shows the result.

Network diagnostics

Spotting whether a round-trip time is40 thousandths or400 thousandths. The visual feedback of fast-changing digits makes latency visible.

Sports and fitness

Interval training where split times matter more than absolute correctness. A gym display board, not a sanctioned competition timer.

ADHD and time blindness support

A visibly ticking clock helps maintain awareness of time passing. The fractional digits provide the motion that makes the passage of time tangible. The precision is not the point. The movement is.

In all these cases, the page is not claiming sub-second accuracy. It is claiming sub-second resolution, and that is enough for the job.

Can browser millisecond displays be misleading?

Sub-second readouts become misleading the moment you mistake them for sub-second accuracy. They are not. The number is real, but it is old, and it is only as correct as the system clock behind it.

The drift problem

Consumer hardware drifts by1 to15 seconds per month between NTP synchronizations. A laptop that has been in sleep mode for a week can be off by seconds. A virtual machine can inherit host clock corrections. If the OS clock is wrong, every clock in the browser is wrong too, including the one showing three decimal places.

When the display crosses the line

A clock showing 14:23:45.123 while true time is 14:23:45.450 is not just imprecise. It is actively deceptive. The resolution implies a precision the system cannot deliver. The page is not broken. The expectations are.

The NTP gap no browser API exposes

No browser API exposes NTP status, stratum level, or last sync time to JavaScript. The page cannot warn you that the OS clock has not synced in three weeks. It can only read what the OS gives it and display the result faithfully. The user sees a precise number and assumes it is accurate. The page has no way to correct that assumption.

What to trust instead

For wall-clock time, trust that the display is a best-effort snapshot, not a measurement. For elapsed time, trust performance.now(). For anything requiring certified accuracy, a browser is the wrong tool. Use a dedicated time server, a GPS-disciplined clock, or calibrated hardware. The browser is for casual reference. Treat it accordingly.