All topics
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,
                  rendering
2
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 B
5
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 3
8
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 permanently
10
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 2
13
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.