Asyncadvanced
Microtasks vs Macrotasks
The two distinct queues the event loop draws from, and why microtasks always run before the next macrotask.
The event loop draws from two queues, and the rule between them decides the order of everything asynchronous.
Macrotasks are like scheduled factory shifts. Microtasks are urgent messages handled instantly mid-shift, and every new urgent message is handled too before the next shift starts.
Key Concepts
1
microtasks Promise callbacks, queueMicrotask,
MutationObserver, await continuations
macrotasks setTimeout, setInterval, I/O, DOM events,
rendering2
The rule: when the call stack empties, drain every microtask, then take one macrotask, then
drain microtasks again.
3
console.log('A');
setTimeout(() => console.log('B'));
Promise.resolve().then(() => console.log('C'));
console.log('D');4
// A D C B5
A and D are synchronous. The stack empties. C is a microtask and runs first. B is a macrotask and
waits.
ADCB
6
Microtasks queued by microtasks run in the same drain.
Promise.resolve().then(() => {
console.log('1');
Promise.resolve().then(() => console.log('2'));
});
setTimeout(() => console.log('3'));7
// 1 2 38
2 was queued during the drain and still runs before the timeout.
2
9
Which is how the loop can be starved.
function spin() { Promise.resolve().then(spin); }
spin(); // the page freezes permanently10
The queue never empties, so no macrotask ever runs — no rendering, no events, no timers. The equivalent
with setTimeout does not do this, because each is a separate macrotask and the loop proceeds between
them.
setTimeout
11
await is a microtask boundary.
async function f() {
console.log('1');
await null; // even awaiting a non-promise
console.log('2'); // runs as a microtask
}
f();
console.log('3');await
12
// 1 3 213
Everything after the first await is deferred, which is why an async function's first half runs
synchronously and the rest does not.
awaitasync
14
Why this matters beyond puzzles. Rendering is a macrotask. Work split with queueMicrotask never yields
to the browser, so the page still freezes. Splitting with setTimeout lets a frame render between chunks —
which is the difference between a responsive page and a locked one.
queueMicrotasksetTimeout
15
What the interviewer is probing.1. "Which queue drains first?" Probing: the ordering rule. Stalls: "Whichever is scheduled
first." Moves up: microtasks drain completely between every macrotask, so promise callbacks run
before any pending setTimeout.
16
2. "What goes in each queue?" Probing: the categories. Stalls: "All async work." Moves up:
promises, queueMicrotask and MutationObserver are microtasks; timers, I/O and events are macrotasks.
17
3. "How can microtasks starve the loop?" Probing: the hazard. Stalls: "They cannot." *Moves
up:* a microtask that queues another microtask keeps the queue non-empty, so rendering and timers
never get a turn.
18
4. "Where does requestAnimationFrame fit?" Probing: the render step. Stalls: "It is a
macrotask." Moves up: it runs before the next paint, between macrotasks, which is why it suits
animation and setTimeout does not.