Event Loop: Understanding JavaScript Execution Model
Learn the interview-ready mental model, practical trade-offs, and production patterns for this web fundamentals topic.
Topic content
The JavaScript Event Loop manages execution order: current synchronous code runs to completion on the Call Stack, then all Microtasks (Promises, queueMicrotask) are drained, followed by one Task (macrotask like setTimeout), then microtasks again, before a rendering opportunity. This model explains why Promise callbacks run before setTimeout callbacks.
The Call Stack is the head chef cooking one dish at a time. Microtasks are urgent VIP orders that must be completed before any new regular orders (Tasks). The Event Loop is the manager who ensures the kitchen finishes all urgent work before starting the next regular order, and checks for rendering (UI updates) between cycles.
1Core Components
Call Stack: runs synchronous JS to completion. Microtask Queue: high priority (Promise.then, queueMicrotask). Task Queue (Macrotasks): lower priority (setTimeout, DOM events). Event Loop: coordinates the flow.
console.log('Sync');
Promise.resolve().then(() => console.log('Microtask'));
setTimeout(() => console.log('Task'), 0);2Execution Order Rules
1. Run current script fully. 2. Drain ALL microtasks. 3. Take ONE task from task queue. 4. Drain microtasks again. 5. Render opportunity. Repeat. Microtasks queued during draining still run before the next task.
3Step-by-Step Example
For the classic example with logs 1,7 + Promise + queueMicrotask + multiple setTimeouts, the output is: 1 7 3 6 5 2 8 4. Microtasks run before any timers, and nested microtasks are processed in the same drain phase.
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => {
console.log('3');
setTimeout(() => console.log('4'), 0);
Promise.resolve().then(() => console.log('5'));
});
queueMicrotask(() => console.log('6'));
console.log('7');
setTimeout(() => console.log('8'), 0);4Microtasks vs Tasks
Microtasks (Promise.then, queueMicrotask, MutationObserver) run immediately after sync code and between tasks. Tasks (setTimeout, events, network callbacks) run one at a time with rendering opportunities in between.
- ✓Synchronous code always runs first on the Call Stack
- ✓Microtasks are drained completely before any new task or rendering
- ✓setTimeout(0) does NOT mean immediate — it waits for microtasks
- ✓Nested microtasks still run in the same drain phase
- ✓Keep tasks short to avoid blocking the main thread and rendering