Case 1/15

Prefer reading? The full explanation

How the Event Loop Runs

setTimeout(fn, 0) · Browser

After this you can explain

Why a 0ms timer still runs after every synchronous line.

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

In the wild

Where this rule bites real code

The "give the browser a moment" hack that works by accident

Symptom
Someone wraps code in setTimeout(fn, 0) "to let the DOM update first" — and it works, but nobody on the team can say why, so nobody dares remove it.
Why
The 0ms timer pushes fn into a NEW task, and the browser gets its chance to run pending work (and often a render) between tasks. The delay value is irrelevant; the task boundary is everything.
Fix
Make the intent explicit: requestAnimationFrame to run after the next paint starts, queueMicrotask to run before it. A bare setTimeout(0) is the vaguest possible way to say "later".

Environment

Browser

CI verified

Node.js v22.22.3

Last reviewed

29 July 2026

The animation on this page is a teaching model, not a diagram of browser internals. Real engines use several task queues, optimise aggressively, and do plenty of work these panels do not show. What is guaranteed is the observable ordering: the stated output is produced by actually executing the code in CI, on the runtime named above. Browser-specific behaviour follows the HTML Standard and is not executed in a real browser by CI. How this is verified →