Event Loop & Task Queues Module:
The 3 Core Behaviors You Need to Know:
1. Call Stack Rules (Synchronous World):
- Stack executes ONE thing at a time, top to bottom
- NOTHING else happens until stack is empty
- All sync code (loops, functions, calculations) blocks everything
2. Queue Priority System (Asynchronous World):
- Microtask Queue = VIP lane (Promises, queueMicrotask)
- Callback Queue = Regular lane (setTimeout, setInterval, DOM events)
- Animation Queue = Special lane (requestAnimationFrame - 60fps timing)
3. Event Loop’s Simple Rules:
javascript
// The Loop's Decision Making:
1. Is call stack empty? If NO → wait
2. If YES → Check microtasks first (drain completely)
3. Then check callback queue (take one)
4. Then render if needed
5. Repeat foreverThe Execution Order Rules (Your Cheat Sheet):
javascript
console.log('1'); // Call stack - runs immediately
setTimeout(() => console.log('2'), 0); // Callback queue
Promise.resolve().then(() => console.log('3')); // Microtask queue
console.log('4'); // Call stack - runs immediately
// Output: 1, 4, 3, 2Why this order?
- Sync code (1, 4) runs first - call stack
- Microtasks (3) run before callbacks - priority rule
- Callbacks (2) run last - lowest priority
Interview Gotchas & Nesting Rules:
Gotcha 1: Microtasks always win
javascript
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => {
console.log('promise');
setTimeout(() => console.log('nested timeout'), 0);
});
// Output: promise, timeout, nested timeoutGotcha 2: Nested microtasks create “starvation”
javascript
function recursiveMicrotask() {
Promise.resolve().then(() => {
console.log('microtask');
recursiveMicrotask(); // Creates infinite microtasks
});
}
setTimeout(() => console.log('I will never run'), 0);
recursiveMicrotask(); // Blocks everything!Gotcha 3: Complex nesting - just follow the rules
javascript
setTimeout(() => {
console.log('outer timeout');
Promise.resolve().then(() => {
console.log('inner promise');
setTimeout(() => console.log('deepest timeout'), 0);
});
}, 0);
Promise.resolve().then(() => {
console.log('outer promise');
setTimeout(() => console.log('inner timeout'), 0);
});
// Output: outer promise, outer timeout, inner promise, inner timeout, deepest timeoutYour Mental Framework for Any Complexity:
Step 1: Identify all sync code → runs immediately in order Step 2: Find all async code → sort by queue type Step 3: Apply priority: Microtasks drain completely, then one callback, then repeat
For nested scenarios: When something executes, it can ADD new tasks to queues. Just re-apply the rules from that point.
Bottom Line:
Event loop is like a traffic controller with strict rules: Sync code goes first, microtasks are VIP, callbacks wait in line. No matter how deep the nesting, just follow the queue priority and you’ll never get tricked. The key is understanding that each execution can add NEW tasks to queues - just re-apply the same rules from that moment.
The Box Method (Your Interview Superpower):
original question :
setTimeout(() => {
console.log('outer timeout');
Promise.resolve().then(() => {
console.log('inner promise');
setTimeout(() => console.log('deepest timeout'), 0);
});
}, 0);
Promise.resolve().then(() => {
console.log('outer promise');
setTimeout(() => console.log('inner timeout'), 0);
});
// Output: outer promise, outer timeout, inner promise, inner timeout, deepest timeout
Draw 3 boxes on paper/whiteboard:
[Call Stack] [Microtask Queue] [Callback Queue]
| | |
| | |
Step-by-Step Tracking for Your Example:
Initial State - JS reads the code:
setTimeout(() => { /* outer timeout code */ }, 0); // Found first!
Promise.resolve().then(() => { /* outer promise code */ }); // Found second!Box State After Reading:
[Call Stack] [Microtask Queue] [Callback Queue]
empty outer promise outer timeout
Event Loop Starts - Rule: Microtask ALWAYS first
Step 1: Execute outer promise
[Call Stack] [Microtask Queue] [Callback Queue]
outer promise code empty outer timeout
Inside outer promise: console.log('outer promise') + new setTimeout
- Log prints: “outer promise”
- New setTimeout goes to callback queue
Box State After Step 1:
[Call Stack] [Microtask Queue] [Callback Queue]
empty empty 1. outer timeout
2. inner timeout
Step 2: Execute outer timeout (first in callback queue)
[Call Stack] [Microtask Queue] [Callback Queue]
outer timeout code empty inner timeout
Inside outer timeout: console.log('outer timeout') + new Promise
- Log prints: “outer timeout”
- New Promise goes to microtask queue
Box State After Step 2:
[Call Stack] [Microtask Queue] [Callback Queue]
empty inner promise 1. inner timeout
2. deepest timeout
Step 3: Execute inner promise (microtask has priority!)
- Log prints: “inner promise”
- New setTimeout goes to callback queue
Final executions:
- “inner timeout” (callback queue position 1)
- “deepest timeout” (callback queue position 2)
Your Simple Rules to Never Get Confused:
Rule 1: Read & Sort First
- Don’t execute anything mentally
- Just put code in the right boxes
Rule 2: Execute One Box at a Time
- Microtask box → drain completely
- Callback box → take ONE item
- Repeat
Rule 3: When Code Executes, It Can Add New Items
- Just update your boxes
- Apply rules again
Quick Practice Method:
For any complex code:
- Scan - put everything in boxes first
- Execute - follow box priority
- Update - when code runs, it might add new items to boxes
- Repeat - keep following the rules
Mental Shortcut for Interviews:
Instead of thinking “what executes when”: Think “what’s in each box right now”
The brain trick:
- Don’t try to predict the future
- Just manage the current state of your 3 boxes
- Let the event loop rules handle the rest
Bottom Line: Use the box method on paper during interviews. It transforms chaos into a simple sorting + priority system. Your brain won’t get overwhelmed because you’re just moving items between boxes, not trying to simulate a computer!
