Microtasks vs Macrotasks
Every asynchronous callback in Node lands in one of two categories: a microtask or a macrotask. The difference governs when it runs relative to everything else — and getting it wrong leads to bugs where code runs in a baffling order. This page pins down the distinction precisely.
Which is which
Microtasks (high priority) | Macrotasks (one per phase) |
|---|---|
|
|
|
|
| I/O callbacks (fs, net, …) |
|
|
The golden rule of ordering
The drain cycle
run one macrotask (or the initial sync script)
│
▼
drain microtask queue COMPLETELY ← incl. microtasks queued during the drain
│
▼
run the NEXT single macrotask
│
▼
drain microtask queue COMPLETELY …and so onSeeing it in action
console.log('1: sync')
setTimeout(() => console.log('2: macrotask'), 0)
Promise.resolve()
.then(() => console.log('3: microtask'))
.then(() => console.log('4: microtask chained'))
console.log('5: sync')1: sync 5: sync 3: microtask 4: microtask chained 2: macrotask
Both sync logs run, then the whole promise chain (3 then 4) drains as microtasks, and only then the setTimeout macrotask (2). The chained .then (4) jumps ahead of the timer even though it was queued later — because it's a microtask.
Microtask starvation
// ✗ STARVATION — this timer NEVER fires
function loop() { Promise.resolve().then(loop) }
loop()
setTimeout(() => console.log('I never run'), 0)
// ✓ SAFE — yields to the loop each iteration
function safeLoop() { setImmediate(safeLoop) }Why this matters in practice
Code after
awaitis a microtask — it resumes before any pendingsetTimeout, which surprises people timing things.Need to yield to I/O / let the loop breathe? Use a macrotask (
setImmediate), not a promise.Need to defer until "right after this code, before anything else"? Use a microtask (
queueMicrotask) orprocess.nextTick.Don't build unbounded microtask recursion — break it up with
setImmediateso timers and I/O still run.
queueMicrotask vs nextTick vs setImmediate
API | Queue | Runs |
|---|---|---|
| nextTick (highest) | Before promises, before next phase |
| Microtask | With promise reactions |
| Macrotask (check phase) | After current poll phase |
| Macrotask (timers phase) | Next tick, ≥1ms later |
The two micro-level schedulers — nextTick and queueMicrotask — and their relationship to setImmediate are explored further in setImmediate & process.nextTick.