Skip to content

Microtasks & Macrotasks

The event loop processes async callbacks from two distinct queues with different priorities:

Microtask queue — processed after every task, before the next macrotask. Sources:

  • Promise .then / .catch / .finally callbacks
  • queueMicrotask(fn)

Macrotask queue (also called the task queue) — one task is processed per event loop turn. Sources:

  • setTimeout callbacks
  • setInterval callbacks
  • I/O callbacks (network, file system)

The rule is simple: after each macrotask completes, the engine drains the entire microtask queue before picking up the next macrotask.

┌─────────────────────────────┐
│ Execute one macrotask │
│ (or the initial script) │
└──────────────┬──────────────┘
┌─────────────────────────────┐
│ Drain all microtasks │◄──┐
│ (including newly added) │ │ new microtasks
└──────────────┬──────────────┘───┘
┌─────────────────────────────┐
│ Pick next macrotask │
└─────────────────────────────┘

Node-specific: process.nextTick and setImmediate

Section titled “Node-specific: process.nextTick and setImmediate”

Node.js adds two more scheduling APIs that require the Node runtime:

// These only work in Node.js — not in browsers
process.nextTick(function() {
console.log('nextTick fires before other microtasks');
});
setImmediate(function() {
console.log('setImmediate fires after I/O callbacks in the current loop iteration');
});

process.nextTick callbacks are drained before the microtask queue. setImmediate fires in the check phase of Node’s event loop. Because these require the Node runtime, they are not available in the in-browser runner below — run them locally or in StackBlitz.

This demo prints four lines. Predict the order before running:

  1. The synchronous console.log runs immediately on the call stack.
  2. setTimeout(..., 0) queues a macrotask.
  3. Promise.resolve().then(...) queues a microtask (the Promise is already resolved, so the reaction is queued synchronously).
  4. queueMicrotask(...) queues another microtask — after the Promise reaction already queued above.

After the synchronous script finishes, all microtasks drain in order, then the macrotask fires.

JavaScript

The output is always in this order:

  • 1 sync — call stack
  • 2 microtask (Promise) — first microtask queued
  • 3 microtask (queueMicrotask) — second microtask queued
  • 4 macrotask — runs only after the microtask queue is empty
Given this code — what prints first: the Promise .then callback or the setTimeout callback?
What does queueMicrotask(fn) do?
If a microtask callback itself calls queueMicrotask, when does the new microtask run?