Under The Hood
backend nodejs Node.js 18+ (libuv 1.x)

Node.js event loop: phases and the microtask queue

Last updated
Prerequisites:
JavaScript async basics
Understanding of callbacks and promises
  • nodejs
  • event-loop
  • libuv
  • microtasks
  • async
  • javascript

Read at your depth

The practical view

Node runs a single thread executing the event loop: at each iteration it processes phases in order — timers (setTimeout/setInterval callbacks ready), pending callbacks (deferred I/O), idle/prepare (internal), poll (I/O events, where it blocks waiting), check (setImmediate), close (close handlers). Between every phase — and after every callback — the microtask queue drains first: process.nextTick callbacks run before Promise.then callbacks. That means setTimeout(fn, 0) does not run before a resolved Promise: microtasks win. setImmediate vs setTimeout(0): setImmediate fires in the check phase of the current iteration; setTimeout(0) in the timers phase of the next — so inside an I/O callback, setImmediate reliably runs first.

Legacy vs modern

setTimeout(0) vs setImmediate vs microtasks ordering

Relying on timer ordering produces race-prone output; knowing the phases and microtask drain explains and fixes the ordering deterministically.

before → after
Timer soup
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
Promise.resolve().then(() => console.log('promise'));
Phase-aware ordering
queueMicrotask(() => console.log('microtask'));
setImmediate(() => console.log('immediate'));
setTimeout(() => console.log('timer'), 1);

Interview gotchas

Under The Hood — a multi-depth technical interview hub.

Press ⌘ K to search.