Microtasks vs Macrotasks
Node schedules async work in two queue families - microtasks (Promises, queueMicrotask, process.nextTick) and macrotasks (timers, setImmediate) - and the drain order explains subtle timing bugs.
Search across all documentation pages
Node schedules async work in two queue families - microtasks (Promises, queueMicrotask, process.nextTick) and macrotasks (timers, setImmediate) - and the drain order explains subtle timing bugs.
Ordering cheat sheet (simplified):
1. Run call stack to empty
2. Drain ALL process.nextTick callbacks
3. Drain ALL microtasks (Promises, queueMicrotask)
4. Run ONE macrotask (timer, I/O, setImmediate, ...)
5. Repeat from step 2
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
setTimeout(() => console.log('timer'), 0);
// nextTick → promise → timerWhen to reach for this:
awaitnextTick, queueMicrotask, and setImmediateimport { setTimeout, setImmediate } from 'node:timers';
async function chain(): Promise<void> {
console.log('A');
await Promise.resolve();
console.log('B');
}
chain();
Promise.resolve().then(() => console.log('C'));
process.nextTick(() => console.log('D'));
setTimeout(() => console.log('E'), 0);
console.log('F');
// A, F, D, C, B, E (B may interleave with C depending on await timing)// Dangerous: recursive nextTick starves I/O
function spin(count: number): void {
if (count <= 0) return;
process.nextTick(() => spin(count - 1));
}
// spin(1_000_000); // Never do this in productionWhat this demonstrates:
A, F) runs before any queued async worknextTick precedes Promise microtasksawait splits async functions into microtask continuationsnextTick prevents macrotasks (including I/O) from runningsetImmediate, etc.process.nextTick is technically separate from the Promise microtask queue but runs before it.queueMicrotask schedules spec-compliant microtasks - prefer it over nextTick for generic deferral.| API | Queue type | Runs before |
|---|---|---|
process.nextTick | nextTick | Promises, timers |
Promise.then / await | microtask | Next macrotask |
queueMicrotask | microtask | Next macrotask |
setTimeout / setInterval | macrotask (timers) | Next loop tick |
setImmediate | macrotask (check) | Next iteration after poll |
// Safe deferral - does not preempt Promise queue like nextTick
function defer(fn: () => void): void {
queueMicrotask(fn);
}
// Run after current I/O batch, not before other microtasks
import { setImmediate } from 'node:timers';
function deferMacrotask(fn: () => void): void {
setImmediate(fn);
}process.nextTick - starves timers and I/O indefinitely. Fix: use setImmediate for batching or a worker.await makes code "parallel" - it only yields microtasks on the same thread. Fix: use workers for CPU parallelism.nextTick with Promise chains for ordering hacks - fragile across Node versions. Fix: restructure with explicit state machines or queues.try/catch around await in event handlers.| Alternative | Use When | Don't Use When |
|---|---|---|
queueMicrotask | Spec-compliant deferral after current stack | You need check-phase timing (setImmediate) |
setImmediate | Batch work after I/O poll | You need before-next-macrotask Promise ordering |
| Job queue (BullMQ) | Cross-process async work | In-process ordering experiments |
async mutex libraries | Serialize access to shared state | Simple one-off deferral |
Microtasks. .then, .catch, .finally, and await continuations all use the microtask queue.
Historical API predating microtasks in Node. nextTick runs before Promises - sometimes used in streams internals; avoid in app code.
All of them - the queue drains completely before the next macrotask starts.
After - setImmediate is a macrotask. All pending microtasks drain first.
An async function always returns a Promise. Code after await schedules as microtasks.
Infinite microtask chains can exhaust memory or starve I/O - similar risk to recursive nextTick.
Browsers lack process.nextTick and setImmediate. setTimeout(0) vs Promise ordering rules are similar.
Yes - it schedules on the microtask queue for the current loop iteration, after the current call stack clears.
Write explicit ordering tests with node:test and log arrays - but prefer eliminating order dependencies in app code.
Microtask continuations after the Promise settles, then further macrotasks for socket I/O underneath.
No - that is a browser API. Node microtasks come from Promises and queueMicrotask.
Rarely - streams legacy internals used it for compatibility. Application code should use queueMicrotask or setImmediate.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 19, 2026