Tips

setTimeout(fn, 0) runs 50ms later? The event loop explains why

TL;DR

JavaScript has a single main thread. setTimeout(fn, 0) just queues the callback as a macrotask; it must wait for the current code to finish and for every microtask (Promise.then) to drain first. So “0ms” can easily mean tens of milliseconds.

Execution order

  1. A task occupies the call stack for 26ms — nothing can cut in.
  2. That task schedules a 0ms timer (into the macrotask queue) and 3 promises (into the microtask queue).
  3. Stack empties → the event loop drains all microtasks first, including newly chained thens.
  4. Only then do macrotasks run: by now the “0ms” timer has waited 50ms.
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
console.log('sync');
// sync → promise → timeout

How to put it to work

  • Don’t use setTimeout(0) for timing or to assume exact ordering — use Promises/await when order matters.
  • Break up heavy work: long tasks block the UI and delay every timer; split them, or use requestIdleCallback or a Web Worker.
  • Beware endless microtasks: promises that keep spawning promises can starve macrotasks and freeze the UI.
  • Node.js adds process.nextTick and its own phases — the microtask-first rule still holds.