🔄
Event Loop
advancedThe Event Loop is how JavaScript handles asynchronous operations despite being single-threaded. It continuously checks if the call stack is empty, then moves callbacks from the task queues to the stack for execution.
🎮Interactive Visualization
CodeSync
1console.log('1');23setTimeout(() => {4 console.log('timeout');5}, 0);67Promise.resolve()8 .then(() => console.log('promise'));910console.log('2');
Call Stack
<script>
Microtasks
(empty)
Macrotasks
(empty)
Output
—
Step 1/10Script starts executing. Global EC pushed to call stack.
Key Insight: Microtasks (Promises) always run before macrotasks (setTimeout), even with 0ms delay!
Key Points
- JavaScript is single-threaded (one call stack)
- Web APIs handle async operations (setTimeout, fetch, etc.)
- Task Queue (Macrotasks): setTimeout, setInterval, I/O
- Microtask Queue: Promises, queueMicrotask, MutationObserver
- Microtasks run before the next macrotask
- Event loop: Stack empty? → Run all microtasks → Run one macrotask → Repeat
💻Code Examples
Promise vs setTimeout
console.log("1");
setTimeout(() => {
console.log("2");
}, 0);
Promise.resolve().then(() => {
console.log("3");
});
console.log("4");
// Output: 1, 4, 3, 2
// Microtasks before macrotasks!Microtasks (Promise) run before macrotasks (setTimeout)
Sync Code Flow
function greet() {
console.log("Hello");
}
console.log("Start");
greet();
console.log("End");
// Output: Start, Hello, End
// Synchronous = line by lineSynchronous code executes line by line on call stack
Chained Promises
Promise.resolve(1)
.then(x => {
console.log(x); // 1
return x + 1;
})
.then(x => {
console.log(x); // 2
return x + 1;
})
.then(x => {
console.log(x); // 3
});
// Each .then queues a microtask
// when the previous resolvesEach .then queues when previous resolves
async/await
async function example() {
console.log("1");
await Promise.resolve();
// Everything after await becomes
// a microtask
console.log("2");
}
console.log("A");
example();
console.log("B");
// Output: A, 1, B, 2await pauses function, queues continuation as microtask
Nested setTimeout
setTimeout(() => {
console.log("First macrotask");
setTimeout(() => {
console.log("Second macrotask");
}, 0);
}, 0);
console.log("Sync");
// Output: Sync, First, Second
// Each setTimeout queues one
// macrotask for next loop cycleNested creates new macrotask for next iteration
Microtask in Macrotask
setTimeout(() => {
console.log("Macro 1");
Promise.resolve().then(() => {
console.log("Micro inside");
});
console.log("Macro 1 end");
}, 0);
setTimeout(() => {
console.log("Macro 2");
}, 0);
// Output: Macro 1, Macro 1 end,
// Micro inside, Macro 2Microtasks created during macrotask run before next macrotask
queueMicrotask
queueMicrotask(() => {
console.log("Microtask 1");
queueMicrotask(() => {
console.log("Microtask 2");
});
});
console.log("Sync");
// Output: Sync, Microtask 1, Microtask 2
// All microtasks drain before
// any macrotask runsDirect microtask scheduling, can queue more microtasks
Microtask Starvation
// DANGER: This blocks forever!
function recursive() {
Promise.resolve().then(recursive);
}
recursive();
setTimeout(() => {
console.log("Never runs!");
}, 0);
// Microtasks keep adding more
// macrotasks are STARVEDInfinite microtasks block all macrotasks forever!
Common Mistakes
- Thinking setTimeout(fn, 0) runs immediately
- Not understanding microtask priority over macrotasks
- Blocking the event loop with long-running synchronous code
Interview Tips
- Draw the event loop diagram (stack, queues, Web APIs)
- Know the order: sync → microtasks → macrotasks
- Explain why Promises are faster than setTimeout