Skip to main content

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

javascript
setTimeout(() => console.log('timeout'), 0); console.log('main');

Output:

text
main timeout

Even 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.

javascript
console.log('A'); setTimeout(() => console.log('B'), 0); console.log('C');

Output:

text
A C B

Zero 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.

javascript
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 over

On 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.
javascript
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 doesQueues the callback for the next turn of the event loop
DelayAt least 0 ms, in practice a little later
ExecutionAfter all synchronous code
Used forDeferred 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 timer undefined.
  • Counting on an exact delay: 0 is 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 before setTimeout(fn, 0).
  • Using setTimeout(fn, 0) for animation instead of requestAnimationFrame and getting janky motion.
  • Not keeping the timer id when the call may need to be cancelled with clearTimeout.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.