Skip to main content

Timer accuracy guarantee

No, you cannot guarantee the exact firing time of a timer in JavaScript (setTimeout, setInterval and the like). The reason is simple: JavaScript is single threaded, and when a timer actually runs depends on the state of the Event Loop and on how busy the call stack is.

Theory

TL;DR

  • A timer only guarantees that the callback will not run earlier than the delay you asked for.
  • While the call stack is busy, the callback waits in the macrotask queue.
  • Even setTimeout(fn, 0) has a minimum delay, about 4 ms in browsers.
  • In an inactive tab the browser deliberately slows timers down, to 1000 ms and beyond.
  • setInterval does not give even intervals either: a slow callback shifts every later tick.
  • For precision there are performance.now(), requestAnimationFrame() and a drift compensating pattern.

Quick example

javascript
console.log('Start'); setTimeout(() => console.log('Timer 1s'), 1000); const start = Date.now(); while (Date.now() - start < 3000) {} // block the thread for 3 seconds console.log('End');

Result:

text
Start (3 seconds of pause) End Timer 1s

Although the timer was set for 1 second, it fired only after 3 seconds. Why? Because the engine could not run it any earlier, the stack was busy with the loop.

Why this happens

When you call:

javascript
setTimeout(fn, 1000);

JavaScript does not promise that fn runs exactly after 1000 ms. It only queues the task so that it runs no earlier than 1000 ms from now.

If the call stack is busy at the moment the timer "fires", the callback waits its turn.

Minimum delay and throttling

Even setTimeout(fn, 0) does not start the code "right away", there is still a minimum delay:

  • in browsers it is usually 4 ms for nested timers, per the HTML Living Standard;
  • in Node.js it can be smaller, but it is not instant either.

On top of that, if the browser tab is inactive the delay can be increased to 1000 ms and more: browsers save resources.

Example with setInterval

setInterval() does not guarantee exact intervals either. If the function inside takes longer than the interval, the calls "accumulate" and run late.

javascript
setInterval(() => { const start = Date.now(); while (Date.now() - start < 1500) {} // block for 1.5 seconds console.log('tick'); }, 1000);

The interval is 1 second but the function takes 1.5 seconds, so the ticks arrive with a real delay of 1.5, 3.0, 4.5 seconds and so on.

Why precision is out of reach

ReasonExplanation
JavaScript is single threadedWhile code is running, other tasks wait
Event LoopTimers only land in the queue, they do not start instantly
Browser limitsThere is a minimum delay, plus throttling in the background
CPU loadUnder heavy load timers run later
OS schedulerJavaScript does not control system time, it only "asks" for the task to run later

What you can do

If you need maximum precision, for example in animation or synchronisation, there are alternatives.

  1. performance.now() measures precise time in milliseconds, with sub millisecond resolution:

    javascript
    const start = performance.now(); // ... const elapsed = performance.now() - start;
  2. requestAnimationFrame() runs the callback before every frame, roughly 60 times a second, which is ideal for smooth animation.

  3. setTimeout with drift compensation, that is, with the delay corrected each time:

    javascript
    const start = Date.now(); let count = 0; function tick() { count++; const expected = start + count * 1000; const drift = Date.now() - expected; console.log(`tick ${count}, drift: ${drift}ms`); setTimeout(tick, 1000 - drift); } tick();

    This approach accounts for the lateness and keeps the intervals even.

Summary

QuestionAnswer
Can you guarantee a timer's exact time?No
Why?Single threaded JavaScript, the Event Loop, minimum delays and load
What really happens?The timer is added to the task queue and runs later
Is there an alternative for precision?Yes: requestAnimationFrame, performance.now, drift compensation

Common mistakes

  • Treating the delay as a schedule. delay is a lower bound, not the moment of execution.
  • Building a clock on setInterval. Over an hour such a counter drifts noticeably; base it on real time from Date.now() or performance.now() and redraw the value instead.
  • Measuring durations with Date.now(). System time can jump, for example during a clock sync; performance.now() is monotonic and more reliable for measurement.
  • Forgetting background tabs. Logic that relies on an even timer tick breaks as soon as the user switches tabs.
  • Animating with setInterval. Frame based animation belongs to requestAnimationFrame, which is synchronised with screen refresh and avoids tearing.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.