Microtasks vs Tasks (Macrotasks)
TL;DR
The Event Loop has two separate queues for asynchronous callbacks. The Microtask Queue is the VIP lane, primarily used for Promise resolutions. The Task Queue (Macrotask) is the slow lane, used for setTimeout and DOM events. The Event Loop will absolutely refuse to process any Macrotasks until the Microtask Queue is 100% empty.
Mental Model
How It Works
What goes in the Microtask Queue?
Promise.then(),.catch(),.finally()queueMicrotask()MutationObserver(in browsers)process.nextTick()(in Node.js, though it technically has its own queue that runs before standard microtasks).
What goes in the Macrotask (Task) Queue?
setTimeout()setInterval()setImmediate()(Node.js)- UI Rendering & DOM Events (Clicks, scrolls)
- I/O operations (Network requests returning)
The Starvation Problem:
Because the Event Loop drains the entire Microtask queue before checking the Macrotask queue, if a Microtask queues another Microtask, which queues another Microtask infinitely, the Macrotask queue will starve. setTimeout callbacks will never run, and the browser UI will completely freeze because it cannot render!
Example
console.log("Start"); // Sync
setTimeout(() => console.log("Timeout 1 (Macro)"), 0);
Promise.resolve().then(() => {
console.log("Promise 1 (Micro)");
// Queueing a Microtask INSIDE a Microtask
Promise.resolve().then(() => {
console.log("Promise 2 (Micro)");
});
});
console.log("End"); // Sync
Output:
- Start
- End
- Promise 1 (Micro)
- Promise 2 (Micro) (The Event Loop stays in the VIP lane!)
- Timeout 1 (Macro)
Common Output Question
setTimeout(() => {
console.log("A");
Promise.resolve().then(() => console.log("B"));
}, 0);
setTimeout(() => {
console.log("C");
}, 0);
Q: What is the exact output order?
A: A, B, C.
Many developers guess A, C, B. That is wrong.
- The first timeout runs, printing
A. - It queues a Promise
Binto the Microtask Queue. - CRITICAL: Before the Event Loop moves on to the next Macrotask (
C), it ALWAYS checks the Microtask Queue. - It sees
Bin the Microtask queue, executes it, and printsB. - Now the Microtask queue is empty again, so it grabs the next Macrotask and prints
C.
Senior Interview Question
Q: Explain the difference between process.nextTick() and setImmediate() in Node.js.
A: The naming is notoriously terrible and backward.
process.nextTick()does NOT wait for the “next tick” of the event loop. It executes immediately, before the Microtask queue is even checked. It is the absolute highest priority async queue in Node.setImmediate()does NOT execute immediately. It queues a Macrotask that executes on the next iteration of the event loop (specifically in the Check phase).
Therefore,nextTickfires before Promises, andsetImmediatefires after I/O events.