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.
setIntervaldoes 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
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:
Start
(3 seconds of pause)
End
Timer 1sAlthough 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:
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.
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
| Reason | Explanation |
|---|---|
| JavaScript is single threaded | While code is running, other tasks wait |
| Event Loop | Timers only land in the queue, they do not start instantly |
| Browser limits | There is a minimum delay, plus throttling in the background |
| CPU load | Under heavy load timers run later |
| OS scheduler | JavaScript 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.
-
performance.now()measures precise time in milliseconds, with sub millisecond resolution:javascriptconst start = performance.now(); // ... const elapsed = performance.now() - start; -
requestAnimationFrame()runs the callback before every frame, roughly 60 times a second, which is ideal for smooth animation. -
setTimeoutwith drift compensation, that is, with the delay corrected each time:javascriptconst 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
| Question | Answer |
|---|---|
| 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.
delayis 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 fromDate.now()orperformance.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 torequestAnimationFrame, which is synchronised with screen refresh and avoids tearing.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.