Event Loop & Microtasks
Key Learning Objectives
Trace asynchronous JavaScript execution across the Call Stack, Microtask Queue, and Macrotask (Task) Queue.
Understand the HTML5 Event Loop specification ordering: Synchronous -> Drain Microtasks -> Render/Paint -> Next Macrotask.
Predict console output ordering with mixed Promises, setTimeout, queueMicrotask, and async/await.
Explain how recursive microtasks can starve the macrotask queue and freeze the browser UI.
The Interview Problem
What is the exact order of numbers printed to the console when the following JavaScript snippet executes?
1console.log(1);23setTimeout(() => {4 console.log(2);5}, 0);67Promise.resolve()8 .then(() => {9 console.log(3);10 return Promise.resolve(4);11 })12 .then((val) => {13 console.log(val);14 });1516queueMicrotask(() => {17 console.log(5);18});1920console.log(6);
Predict Console Output
Select the option that matches what standard ECMAScript prints to the console:
1 6 3 5 4 2
1 2 3 4 5 6
1 6 2 3 5 4
1 6 3 4 5 2
V8 Engine Execution Trace
Step 1 of 9 (Line 1)Synchronous execution: log 1 directly to console.
Deep Technical Breakdown
The HTML Event Loop Processing Model
The Event Loop continuously coordinates execution between three primary components:
- Call Stack (Synchronous Code): All synchronous code executes to completion first. Lines 1 and 20 run immediately, producing
1and6. - Microtask Queue: Populated by
Promise.then/catch/finally,queueMicrotask, andMutationObserver.- Critical Rule: As soon as the Call Stack is empty, the engine enters a Microtask Checkpoint and drains the entire Microtask Queue, including any new microtasks enqueued while processing existing microtasks.
- Microtask 1 runs: logs
3and returnsPromise.resolve(4), scheduling its.thenhandler. - Microtask 2 runs: logs
5. - Microtask 3 runs: receives resolved value 4 and logs
4.
- Macrotask Queue (Task Queue): Populated by
setTimeout,setInterval,setImmediate(Node), and I/O.- A macrotask is only picked AFTER the microtask queue has reached zero tasks. Thus, the
setTimeoutcallback logs2last.
- A macrotask is only picked AFTER the microtask queue has reached zero tasks. Thus, the
Common Traps & Mistakes
Believing `setTimeout(fn, 0)` means zero delay execution. In browsers, timers have a minimum clamp (typically 4ms after nested depth) and always wait behind microtasks.
Assuming `async/await` is fundamentally different from Promises. An `async` function is syntactic sugar over generator-like Promise chaining; code up to the first `await` is synchronous, and the remainder is enqueued as a microtask.
Recursively calling `queueMicrotask` or `Promise.resolve().then(recurse)`, which indefinitely starves the macrotask queue and hangs UI rendering.
FAANG Follow-Up Probes
Probe #1
How does the browser rendering/paint step fit into the event loop cycle relative to microtasks and macrotasks?
Probe #2
How does Node.js's event loop (libuv phases: timers, pending callbacks, poll, check, close) differ from the browser HTML event loop?
Probe #3
Why does `requestAnimationFrame` execute before layout and paint, and how does it relate to macrotask scheduling?
