The Event Loop — How JavaScript Really Works
The event loop is the heart of JavaScript's async model. It coordinates the call stack, callback queue, and microtask queue to handle multiple operations despite being single-threaded. Understanding it is key to predicting code execution order.
Event Loop Explained
- Call Stack — Where synchronous code executes. Functions push/pop (LIFO)
- Web APIs — Browser threads for async operations: setTimeout, fetch, DOM events
- Task Queue (Macrotask) — setTimeout, setInterval, I/O callbacks. Processed one at a time
- Microtask Queue — Promise callbacks (.then), MutationObserver. Processed ALL before next macrotask
- Event Loop — Continuously checks: 1) Is call stack empty? 2) Process ALL microtasks. 3) Process ONE macrotask. Repeat
- Priority — Synchronous code > Microtasks (promises) > Macrotasks (setTimeout)
Event Loop Code
// Event Loop in action — predict the output!
console.log("1. Synchronous");
setTimeout(() => {
console.log("2. setTimeout (macrotask)");
}, 0);
Promise.resolve().then(() => {
console.log("3. Promise (microtask)");
});
console.log("4. Synchronous again");
// Output order: 1, 4, 3, 2
// Why?
// 1. "1. Synchronous" — runs immediately (call stack)
// 2. setTimeout callback → goes to MACROTASK queue
// 3. Promise.then callback → goes to MICROTASK queue
// 4. "4. Synchronous" — runs immediately (call stack)
// 5. Call stack empty → process ALL MICROTASKS → "3. Promise"
// 6. Process ONE MACROTASK → "2. setTimeout"
// More complex example
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve()
.then(() => console.log("C"))
.then(() => console.log("D"));
setTimeout(() => console.log("E"), 0);
console.log("F");
// Output: A, F, C, D, B, E
// Sync first (A, F), Microtasks (C, D), Macrotasks (B, E)
/*
EVENT LOOP VISUALIZATION:
Call Stack: [A] → [F] → empty
Microtask Queue: [C callback] → [D callback]
Macrotask Queue: [B callback] → [E callback]
Loop iteration 1: Stack empty → process ALL microtasks (C, D)
Loop iteration 2: Stack empty → process ONE macrotask (B)
Loop iteration 3: Stack empty → process ONE macrotask (E)
*/Tip
Tip
Microtasks (Promises, queueMicrotask) run before macrotasks (setTimeout, setInterval) in every event loop cycle. This is why Promise.then() always executes before setTimeout(fn, 0) even though both are async.
The Event Loop enables async operations despite JS being single-threaded
Common Mistake
Warning
Creating a long chain of microtasks can starve the macrotask queue. If every microtask creates another microtask, setTimeout callbacks and DOM updates never get a chance to run, causing the page to freeze.
Practice Task
Note
Event loop: (1) Predict output order: console.log, setTimeout, Promise.resolve().then(), queueMicrotask. (2) Run it and verify. (3) Draw the event loop diagram showing call stack, microtask queue, and macrotask queue.
Quick Quiz
Key Takeaways
- The event loop is the heart of JavaScript's async model.
- Call Stack — Where synchronous code executes. Functions push/pop (LIFO)
- Web APIs — Browser threads for async operations: setTimeout, fetch, DOM events
- Task Queue (Macrotask) — setTimeout, setInterval, I/O callbacks. Processed one at a time