setTimeout(fn, 0)
setTimeout(fn, 0) does not run the function immediately, as it may look: it queues the callback fn to run after the current code, that is asynchronously, on the next turn of the event loop. A zero delay does not mean "run now", it means "run as soon as possible, but after all the synchronous code".
Theory
TL;DR
setTimeout(fn, 0)schedules a call, it does not perform it.- The callback runs once the call stack is free, that is after all the current synchronous code.
- The real delay is greater than zero: the specification lets the browser clamp nested timers to about 4 ms.
- The reason is that JavaScript is single threaded: asynchronous callbacks wait in a queue.
- It is used to defer code, to avoid blocking the UI, and to let the browser repaint the DOM.
Quick example
setTimeout(() => console.log('timeout'), 0);
console.log('main');Output:
main
timeoutEven though the delay is zero, JavaScript first runs the whole current stack, everything synchronous, and only then takes callbacks from the timer queue (the callback queue).
Why it works that way
JavaScript is single threaded: it has one call stack and one task queue. Every asynchronous operation (setTimeout, fetch, Promise) goes into the event queue and runs only once the call stack has been freed. The event loop keeps checking: when the stack is empty, it takes the next task from the queue and pushes it onto the stack.
console.log('A');
setTimeout(() => console.log('B'), 0);
console.log('C');Output:
A
C
BZero is not really zero
The delay in setTimeout is a minimum waiting time, not a guaranteed one. The callback cannot start while the stack is busy, so a heavy synchronous operation easily pushes it back by hundreds of milliseconds.
setTimeout(() => console.log('timeout'), 0);
const end = Date.now() + 500;
while (Date.now() < end) {
// busy loop blocks the stack for half a second
}
console.log('main'); // main first, timeout only after the loop is overOn top of that, browsers clamp nested timers: from roughly the fifth level of nesting the minimum delay becomes about 4 ms. In a background tab the interval can be larger still.
What setTimeout(fn, 0) is used for
- Waiting for the current operations to finish before reading the result.
- Keeping the interface responsive: splitting a heavy computation into chunks so the browser can paint a frame in between.
- Running code "after everything else", for example after all handlers of an event have fired.
- Letting the browser update the DOM before the next step.
button.addEventListener('click', () => {
status.textContent = 'Loading...'; // the browser can paint this first
setTimeout(() => {
runHeavyCalculation(); // deferred, so the text is visible
}, 0);
});For animation requestAnimationFrame is the better fit, and to defer until the next microtask use queueMicrotask or Promise.resolve().then(fn).
Summary table
| What it does | Queues the callback for the next turn of the event loop |
|---|---|
| Delay | At least 0 ms, in practice a little later |
| Execution | After all synchronous code |
| Used for | Deferred code, sequencing, avoiding UI blocking |
Common mistakes
- Assuming that
setTimeout(fn, 0)runs the function immediately and the result is available on the next line. - Writing
setTimeout(fn(), 0): the parentheses run the function right away and hand the timerundefined. - Counting on an exact delay:
0is a minimum, the real time depends on how busy the stack is and on the browser. - Confusing it with microtasks:
Promise.resolve().then(fn)runs beforesetTimeout(fn, 0). - Using
setTimeout(fn, 0)for animation instead ofrequestAnimationFrameand getting janky motion. - Not keeping the timer id when the call may need to be cancelled with
clearTimeout.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.