Skip to main content

Macrotask queue

A macrotask queue is the queue of large asynchronous tasks that are executed one per Event Loop iteration. Each macrotask is a "big" event: a timer fired, the user clicked, a network response arrived. The microtask queue has higher priority, so macrotasks always wait their turn.

Theory

TL;DR

  • A macrotask is a "big" asynchronous unit of work: setTimeout, setInterval, setImmediate (Node.js), DOM event handlers, network callbacks, MessageChannel.
  • Exactly one macrotask runs per Event Loop iteration.
  • After each macrotask the engine runs all microtasks that have piled up.
  • The order never changes: macrotask, all microtasks, next macrotask, all microtasks.
  • The macrotask queue is FIFO: tasks run in the order they were added.
  • Macrotasks have lower priority than microtasks, so Promise.then() fires before setTimeout(..., 0).

Quick example

javascript
console.log('1'); setTimeout(() => console.log('2 (macrotask)'), 0); Promise.resolve().then(() => console.log('3 (microtask)')); console.log('4');

Result:

javascript
1 4 3 (microtask) 2 (macrotask)

Why exactly this order:

  1. 1 and 4 are printed synchronously, while the main script runs.
  2. .then() schedules a microtask, which runs as soon as the stack is empty.
  3. setTimeout schedules a macrotask, which runs only on the next Event Loop iteration.

What lands in the macrotask queue

Sources of macrotasks in the browser and in Node.js:

  • setTimeout
  • setInterval
  • setImmediate (Node.js only)
  • DOM event handlers: click, load, input and friends
  • network callbacks: XMLHttpRequest, a completed fetch
  • MessageChannel

What they all share: the task arrives from "outside" the JavaScript engine, from a timer, from I/O or from the user.

How the Event Loop works step by step

A simplified but workable model of the loop:

  1. All synchronous code runs (Call Stack).
  2. Once the stack is empty, microtasks run.
  3. Then the engine takes one macrotask from the queue and executes it.
  4. When that macrotask finishes, all microtasks run: promise callbacks, queueMicrotask, process.nextTick and so on.
  5. Then the next macrotask is taken, and again all microtasks after it.
StageWhat the engine does
1Runs the synchronous call stack
2Runs all microtasks
3Takes one macrotask from the queue
4Runs all microtasks again
5Repeats the cycle

Schematically one tick looks like this:

javascript
// [ Tick 1 ] // Call Stack: synchronous code // v // Microtask Queue: promise callbacks // v // Macrotask Queue: setTimeout, events // v // Event Loop alternates between them

A simple analogy: the Event Loop is a restaurant. Macrotasks are the main courses, served one at a time. Microtasks are the seasonings and side dishes added between courses. After every main course the cook hands out all the seasonings first and only then starts the next order.

Several macrotasks in a row

javascript
setTimeout(() => console.log('A'), 0); setTimeout(() => console.log('B'), 0); setTimeout(() => console.log('C'), 0); console.log('D');

Result:

javascript
D A B C

All three setTimeout() calls land in the macrotask queue and run one at a time, in the order they were added (FIFO). The synchronous console.log('D') is always first because it never goes through a queue at all.

Microtasks inside a macrotask

javascript
setTimeout(() => { console.log('1 (timeout)'); }, 0); setTimeout(() => { console.log('2 (timeout)'); Promise.resolve().then(() => console.log('3 (microtask)')); }, 0);

Result:

javascript
1 (timeout) 2 (timeout) 3 (microtask)

Explanation: after every macrotask the engine runs all microtasks created during that macrotask. Line 3 is a microtask spawned by the second macrotask, so it runs before the engine picks the next macrotask.

Macrotasks versus microtasks

PropertyMacrotaskMicrotask
ExamplessetTimeout, setInterval, DOM events, fetchPromise.then, queueMicrotask, process.nextTick
When they runAfter the microtask queue is emptyAfter every macrotask, or after synchronous code
How many per cycleOne per Event Loop iterationAll of them, until the queue is empty
PriorityLowHigh
Kind of work"External": timers, I/O, events"Internal": JavaScript logic itself

The summary, one line per point:

TermDescription
Macrotask queueThe queue of large events executed by the browser or Node.js
What it storesTimers, I/O, event handlers, network callbacks
How it is drainedOne task per Event Loop iteration
After each oneAll microtasks are executed
PriorityLower than microtasks: Promise fires sooner than setTimeout

Common mistakes

  • Assuming setTimeout(fn, 0) runs fn immediately. Zero is a minimum delay, not a promise. The callback joins the macrotask queue and waits until the synchronous code is done and the microtask queue is empty.
  • Mixing up the priorities. Promise.resolve().then(...) always beats setTimeout(..., 0), even when the promise is created later in the source.
  • Thinking all macrotasks run in one iteration. Only one does. The rest wait for later iterations, which is how the browser finds time to repaint between them.
  • Creating an endless microtask chain. If a microtask schedules another microtask, the queue never empties and no macrotask ever gets a turn: the page freezes.
  • Relying on timer accuracy. A long macrotask blocks the loop, so setTimeout(fn, 100) may fire much later than 100 ms.
  • Confusing setImmediate with setTimeout in Node.js. They belong to different phases of the loop, and their relative order at the top level of a module is not guaranteed.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.