All topics
Asyncbeginner

setTimeout, setInterval, and Timer Precision

Scheduling delayed or repeating code execution, and why the delay argument is a minimum, not a guarantee.

The delay you pass is a minimum, not a promise.

setTimeout is like setting a kitchen timer and doing other chores — the buzzer doesn't yank you away immediately, you finish what you're doing first.

Key Concepts

1
    setTimeout(fn, 100);
2
That means "queue fn as a macrotask no sooner than 100 ms". It runs when the stack is empty and the microtask queue has drained — which may be much later.
fn
3
    setTimeout(() => console.log('timer'), 0);
    for (let i = 0; i < 1e9; i++) {}       // blocks for seconds
    // 'timer' fires only after the loop finishes
4
Nested timeouts are clamped to about 4 ms, and background tabs are throttled to roughly one second or worse. A setTimeout(fn, 0) recursion does not produce a tight loop.
setTimeout(fn, 0)
5
setInterval does not wait for your callback.
    setInterval(async () => {
      await slowRequest();     // takes 2 seconds
    }, 1000);
setInterval
6
Every second, another one starts. They overlap, then pile up.
7
The fix is a self-scheduling timeout.
    async function poll() {
      await slowRequest();
      setTimeout(poll, 1000);   // 1 second AFTER it finished
    }
    poll();
8
Now the interval is between executions rather than between starts, which is what "poll every second" usually meant.
9
Clear your timers.
    const id = setInterval(tick, 1000);
    clearInterval(id);
10
An interval on a component that unmounted keeps running and keeps its closure alive — a leak and a source of updates to something that no longer exists.
11
For animation, use requestAnimationFrame.
    function frame() {
      move();
      requestAnimationFrame(frame);
    }
requestAnimationFrame
12
It runs before the next repaint, synchronised with the display, and pauses when the tab is hidden. A setInterval(fn, 16) drifts against the refresh rate and continues in the background.
setInterval(fn, 16)
13
Measuring elapsed time.
    performance.now()   // monotonic, sub-millisecond, unaffected
                        // by clock changes
    Date.now()          // wall clock, can jump backwards
14
Use performance.now() for durations. Date.now() can move if the system clock is adjusted, which produces negative durations.
performance.now()Date.now()
15
And the general point. Timers are scheduling hints on a single-threaded loop. If precision matters — audio, animation, measurement — use the API built for it rather than a timer.
16
What the interviewer is probing.1. "Does setTimeout(fn, 0) run immediately?" Probing: the guarantee. Stalls: "Yes." *Moves up:* no — it queues a macrotask that runs after the stack clears and microtasks drain, with a minimum clamp of about 4ms when nested.
17
2. "Why prefer recursive setTimeout to setInterval?" Probing: drift and overlap. Stalls: "They are the same." Moves up: setInterval queues regardless of whether the previous run finished, so slow work overlaps; a recursive timeout schedules the next only after completion.
18
3. "What happens to timers in a background tab?" Probing: throttling. Stalls: "They run normally." Moves up: browsers throttle them heavily, often to once per minute, so they cannot drive anything time-critical.
19
4. "How do you clear a timer correctly?" Probing: the handle. Stalls: "It expires." *Moves up:* keep the id returned and pass it to clearTimeout or clearInterval, especially in component cleanup.