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 beforesetTimeout(..., 0).
Quick example
console.log('1');
setTimeout(() => console.log('2 (macrotask)'), 0);
Promise.resolve().then(() => console.log('3 (microtask)'));
console.log('4');Result:
1
4
3 (microtask)
2 (macrotask)Why exactly this order:
1and4are printed synchronously, while the main script runs..then()schedules a microtask, which runs as soon as the stack is empty.setTimeoutschedules 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:
setTimeoutsetIntervalsetImmediate(Node.js only)- DOM event handlers:
click,load,inputand friends - network callbacks:
XMLHttpRequest, a completedfetch 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:
- All synchronous code runs (Call Stack).
- Once the stack is empty, microtasks run.
- Then the engine takes one macrotask from the queue and executes it.
- When that macrotask finishes, all microtasks run: promise callbacks,
queueMicrotask,process.nextTickand so on. - Then the next macrotask is taken, and again all microtasks after it.
| Stage | What the engine does |
|---|---|
| 1 | Runs the synchronous call stack |
| 2 | Runs all microtasks |
| 3 | Takes one macrotask from the queue |
| 4 | Runs all microtasks again |
| 5 | Repeats the cycle |
Schematically one tick looks like this:
// [ Tick 1 ]
// Call Stack: synchronous code
// v
// Microtask Queue: promise callbacks
// v
// Macrotask Queue: setTimeout, events
// v
// Event Loop alternates between themA 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
setTimeout(() => console.log('A'), 0);
setTimeout(() => console.log('B'), 0);
setTimeout(() => console.log('C'), 0);
console.log('D');Result:
D
A
B
CAll 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
setTimeout(() => {
console.log('1 (timeout)');
}, 0);
setTimeout(() => {
console.log('2 (timeout)');
Promise.resolve().then(() => console.log('3 (microtask)'));
}, 0);Result:
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
| Property | Macrotask | Microtask |
|---|---|---|
| Examples | setTimeout, setInterval, DOM events, fetch | Promise.then, queueMicrotask, process.nextTick |
| When they run | After the microtask queue is empty | After every macrotask, or after synchronous code |
| How many per cycle | One per Event Loop iteration | All of them, until the queue is empty |
| Priority | Low | High |
| Kind of work | "External": timers, I/O, events | "Internal": JavaScript logic itself |
The summary, one line per point:
| Term | Description |
|---|---|
| Macrotask queue | The queue of large events executed by the browser or Node.js |
| What it stores | Timers, I/O, event handlers, network callbacks |
| How it is drained | One task per Event Loop iteration |
| After each one | All microtasks are executed |
| Priority | Lower than microtasks: Promise fires sooner than setTimeout |
Common mistakes
- Assuming
setTimeout(fn, 0)runsfnimmediately. 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 beatssetTimeout(..., 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
setImmediatewithsetTimeoutin 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 readyA concise answer to help you respond confidently on this topic during an interview.