Microtask Queue vs Macrotask Queue in JavaScript
Two queues, different priorities. Here is why promises always run before timers.
Microtask Queue vs Macrotask Queue in JavaScript
JavaScript has two callback queues with different priorities. Understanding them explains all async timing.
Microtask Queue
Contains callbacks from:
Promise.then,.catch,.finallyqueueMicrotask(fn)MutationObserverprocess.nextTick(Node.js, even higher priority than promises)
The event loop drains all microtasks before any macrotask and before rendering. This means if you keep adding microtasks, macrotasks never run.
Macrotask Queue (Task Queue)
Contains callbacks from:
setTimeout,setIntervalsetImmediate(Node.js)- I/O callbacks (network, file system)
- UI events (click, input, scroll)
MessageChannel,postMessage
The event loop takes one macrotask, then drains all microtasks generated by it, then takes the next macrotask.
Priority
setTimeout(() => console.log("timeout"), 0); Promise.resolve().then(() => console.log("promise")); queueMicrotask(() => console.log("microtask")); `` **Output**: `promise, microtask, timeout` (or `microtask, promise, timeout` microtasks run in FIFO order). All microtasks run before the macrotask (`timeout`). ### Why the Distinction? - **Microtasks** are for short, high-priority work that should run before rendering (promise resolution). - **Macrotasks** are for longer, user-facing work (timers, events, I/O). ### Starvation ```js function recursive() { Promise.resolve().then(recursive); } recursive(); setTimeout(() => console.log("never"), 0); // "never" is never logged microtasks starve the macrotask queue `` If microtasks keep adding more microtasks, macrotasks never run. The page freezes. ### Rendering The browser renders between macrotasks. Microtasks run before rendering, so long microtasks can delay the paint. ### The Takeaway Microtasks (promises, queueMicrotask) are drained completely before any macrotask and before rendering. Macrotasks (timers, events, I/O) run one at a time with microtasks drained after each. Microtasks can starve macrotasks. This priority difference explains why promises always run before setTimeout.
Microtasks (Promise.then, queueMicrotask) are drained completely before any macrotask and before rendering. Macrotasks (setTimeout, events, I/O) run one at a time with microtasks drained after each. Microtasks have higher priority.
Because promise callbacks go to the microtask queue, which the event loop drains completely before taking any macrotask from the macrotask queue where setTimeout callbacks live. Microtasks always run first.
Promise.then, .catch, .finally callbacks, queueMicrotask(fn), MutationObserver callbacks, and process.nextTick (Node.js, even higher priority than promises).
setTimeout, setInterval, setImmediate (Node.js), I/O callbacks (network, file system), UI events (click, input, scroll), MessageChannel, and postMessage callbacks.
Yes. If a microtask keeps adding more microtasks (recursive Promise.then), the event loop never reaches the macrotask queue. setTimeout callbacks never run. The page freezes. This is microtask starvation.
Ready to master React completely?
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.
Master React
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course.

