Web Fundamentals

Event Loop: Understanding JavaScript Execution Model

mediumWeb Fundamentals

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

TL;DRSynchronous → Drain all Microtasks → One Task → Drain Microtasks → Render → Repeat
Very High Signal
Google
Meta
Zeta
Rippling
30-Second Answerstart every interview with this

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.

Call Stack (Sync Code)
Drain Microtask Queue (fully)
Run 1 Task (macrotask)
Drain Microtask Queue again
Render Opportunity (rAF + Paint)
Repeat

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.

event-loop.jsjs
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.

example.jsjs
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.

Key Takeaways
  • 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