The Event Loop
TL;DR
JavaScript has only one Call Stack, meaning it can only do one thing at a time. The Event Loop is the orchestrator that allows JavaScript to perform non-blocking asynchronous operations. It constantly monitors the Call Stack and the Task Queues, moving callbacks into the Call Stack for execution only when the stack is completely empty.
Mental Model
How It Works
- Synchronous code is executed immediately on the Call Stack.
- When an async operation (like
setTimeoutorfetch) is encountered, the Call Stack instantly offloads it to the Web APIs and continues running the next line of code. - When the Web API finishes its work (e.g., the timer hits 0), it pushes the associated callback function into a Queue.
- Promise callbacks go to the Microtask Queue.
setTimeoutand DOM events go to the Macrotask Queue.
- The Event Loop constantly checks the Call Stack. If the stack is NOT empty, the loop waits.
- The moment the Call Stack is empty, the Event Loop takes the oldest callback from the Microtask Queue and pushes it onto the Call Stack. It empties the entire Microtask queue first before ever touching the Macrotask queue.
Example
console.log("1. Start (Sync)");
setTimeout(() => {
console.log("2. Timeout (Macrotask)");
}, 0);
Promise.resolve().then(() => {
console.log("3. Promise (Microtask)");
});
console.log("4. End (Sync)");
Execution Order:
console.log("1. Start")runs on Stack.setTimeoutis sent to Web API. Web API instantly sends the callback to the Macrotask Queue (because time is 0).Promise.resolveinstantly sends its.thencallback to the Microtask Queue.console.log("4. End")runs on Stack.- Stack is now empty!
- Event loop checks Microtask Queue. It finds the Promise callback. Pushes it. Prints “3. Promise”.
- Stack is empty. Microtask queue is empty.
- Event loop checks Macrotask Queue. Finds the Timeout callback. Pushes it. Prints “2. Timeout”.
Final Output: 1, 4, 3, 2.
Common Output Question
setTimeout(() => console.log("A"), 0);
Promise.resolve().then(() => {
console.log("B");
setTimeout(() => console.log("C"), 0);
});
Promise.resolve().then(() => console.log("D"));
console.log("E");
Q: What is the exact output order?
A: E, B, D, A, C.
Eis sync.- Promises resolve next.
BandDare in the Microtask queue. - When
Bruns, it queues another timeout (C). - Now the Microtask queue is empty. The Event loop moves to the Macrotask queue.
Awas queued first. ThenCwas queued second. So it printsA, thenC.
Senior Interview Question
Q: If setTimeout(fn, 0) is supposed to run immediately after the stack clears, why is it sometimes delayed by several milliseconds in the browser?
A: The HTML5 specification dictates that if setTimeout is nested more than 5 levels deep, the browser will enforce a minimum delay of 4ms, even if you specify 0. Furthermore, setTimeout is NOT a guaranteed exact execution time; it is a minimum delay. If the Call Stack is blocked by a heavy while loop (or React rendering a massive DOM tree), the Event Loop cannot move the setTimeout callback onto the stack. A 0ms timer might actually take 500ms to execute if the main thread is blocked.