Prefer reading? The full explanation
How the Event Loop Runs
setTimeout(fn, 0) · Browser
console.log('1: Start');
setTimeout(() => {
console.log('2: Timeout Callback');
}, 0);
console.log('3: End'); Output: 1: Start → 3: End → 2: Timeout Callback
Why doesn't setTimeout(fn, 0) run immediately?
The output is 1: Start → 3: End → 2: Timeout Callback. A zero delay reads like "run this now", but setTimeout does not execute anything itself. It asks the browser to start a timer and returns right away, in microseconds. The callback only runs later, once everything else in the script has finished.
So the delay argument answers a narrower question than most people assume. It says when the callback becomes eligible to run, not when it runs.
One call stack, no interruptions
JavaScript executes on a single thread with a single call stack, and synchronous code runs until that stack is empty. An expired timer cannot interrupt line 7 halfway through.
In this example the timer callback may well be sitting in the task queue before 3: End has printed. It still waits. The event loop only moves a queued callback onto the stack after the stack empties, so being ready and being running are two different states.
What the delay actually guarantees
setTimeout(fn, delay) promises at least delay milliseconds, nothing more precise. If the main thread stays busy for three seconds, a 0ms callback waits three seconds. Browsers layer their own rules on top: timers nested more than five levels deep get clamped to roughly 4ms, and background tabs may throttle timers to once per second or less.
The experiment above changes the delay to 1000ms. The countdown sits in the Web APIs panel long after the script has exited, and the output order stays exactly the same. Only the waiting time changed.
Reading about it is one thing. Watching it run is another.
↑ Run it step by step