Event loop in js
The Event Loop & Concurrency Model
Microtasks, Macrotasks, Call Stack coordination, and browser render cycles.
The Busy Restaurant and the Diligent Waiter
JavaScript is single-threaded: it has one call stack. The Event Loop enables non-blocking asynchronous concurrency by coordinating the execution of code, collecting and processing events, and executing queued sub-tasks.
1. Synchronous Execution in Call Stack
All top-level code and function calls run synchronously on the main thread until the Call Stack is completely empty.
2. Web APIs Background Delegation
Async operations (`setTimeout`, DOM events, `fetch`) run on browser background C++ threads, keeping the UI responsive.
3. Microtask Queue Processing
Microtasks come from Promises (`.then`, `.catch`, `.finally`), `queueMicrotask()`, and `MutationObserver`. The Event Loop drains the ENTIRE Microtask queue before picking any Macrotask.
4. Macrotask (Task) Processing
Macrotasks come from `setTimeout`, `setInterval`, `setImmediate` (Node), and I/O events. The loop picks exactly ONE macrotask per tick.
5. Browser Render Pipeline
Between macrotasks, the browser can execute `requestAnimationFrame` and recalculate layout/styles to render frames at 60fps/120fps.
The ultimate interview execution order puzzle:
console.log("1. Sync script start");
setTimeout(() => {
console.log("6. Macrotask: setTimeout 0");
}, 0);
Promise.resolve().then(() => {
console.log("3. Microtask 1: Promise.then");
queueMicrotask(() => {
console.log("4. Microtask 2: nested queueMicrotask");
});
}).then(() => {
console.log("5. Microtask 3: Promise chain");
});
console.log("2. Sync script end");
1. Sync script start
2. Sync script end
3. Microtask 1: Promise.then
4. Microtask 2: nested queueMicrotask
5. Microtask 3: Promise chain
6. Macrotask: setTimeout 0
Microtask Starvation (UI Freeze)
The Risk: Recursively queueing microtasks (`function starve() { queueMicrotask(starve); }`) continuously refills the microtask queue, completely locking the main thread and preventing DOM rendering or user clicks.
The Fix: Break long-running loops across macrotask ticks using `setTimeout(fn, 0)` or `scheduler.yield()`.
- ✓ Execution Order: Synchronous Code → ALL Microtasks → ONE Macrotask → UI Render.
- ✓ Microtasks: Promises (`then/catch/finally`), `queueMicrotask()`, `MutationObserver`.
- ✓ Macrotasks: `setTimeout`, `setInterval`, I/O, UI event handlers.
- ✓ The microtask queue is drained completely to empty on every tick.
- ✓ Browsers render UI between macrotasks, never in the middle of microtasks.