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 finishes4
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 backwards14
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.