Docs
12.4-Event-Loop
6.4 The Event Loop
Introduction
JavaScript is single-threaded, meaning it can only execute one piece of code at a time. Yet it handles asynchronous operations like timers, network requests, and user interactions seamlessly. The secret? The Event Loop.
The JavaScript Runtime Model
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β JAVASCRIPT RUNTIME β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββββββ βββββββββββββββββββββββββββββββ β
β β CALL STACK β β WEB APIs β β
β β β β (Browser-provided) β β
β β βββββββββββββ β β β β
β β β current β β βββΊ β β’ setTimeout/setInterval β β
β β βββββββββββββ€ β β β’ fetch / XMLHttpRequest β β
β β β waiting β β β β’ DOM events β β
β β βββββββββββββ€ β β β’ geolocation β β
β β β global β β β β β
β β βββββββββββββ β βββββββββββββββββββββββββββββββ β
β βββββββββββββββββββ β β
β β β β
β β β β
β β βββββββββββββββββββββββββββββββββββββββ β
β β β CALLBACK QUEUES β β
β β β β β
β β β Macrotask Queue (Task Queue): β β
β β β [callback1] [callback2] [...] β β
β β β β β
β β β Microtask Queue: β β
β β β [promise1] [promise2] [...] β β
β β βββββββββββββββββββββββββββββββββββββββ β
β β β β
β β β β
β ββββββββββββ EVENT LOOP βββββββ β
β (continuously checks) β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
How the Event Loop Works
The Basic Algorithm:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β EVENT LOOP ALGORITHM β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β REPEAT FOREVER: β
β β
β 1. Is the Call Stack empty? β
β βββ NO β Keep running current code β
β βββ YES β Go to step 2 β
β β
β 2. Are there microtasks? β
β βββ YES β Run ALL microtasks β
β (promises, queueMicrotask) β
β βββ NO β Go to step 3 β
β β
β 3. Is it time to render? (browser only) β
β βββ YES β Render (requestAnimationFrame, paint) β
β βββ NO β Go to step 4 β
β β
β 4. Are there macrotasks? β
β βββ YES β Run ONE macrotask β
β (setTimeout, setInterval, I/O) β
β βββ NO β Wait for new tasks β
β β
β 5. Go back to step 1 β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Synchronous vs Asynchronous
Synchronous (Blocking):
console.log('First');
console.log('Second');
console.log('Third');
// Output: First, Second, Third (in order)
Asynchronous (Non-Blocking):
console.log('First');
setTimeout(() => {
console.log('Second'); // Async - goes to queue
}, 0);
console.log('Third');
// Output: First, Third, Second
setTimeout and the Event Loop
console.log('1. Start');
setTimeout(() => {
console.log('3. Timeout');
}, 0);
console.log('2. End');
Step-by-Step Execution:
Step 1: Execute "console.log('1. Start')"
βββββββββββββββββ βββββββββββββββββ
β Call Stack β β Task Queue β
β β β β
β console.log() β β (empty) β
β global β β β
βββββββββββββββββ βββββββββββββββββ
Output: "1. Start"
Step 2: Execute "setTimeout(...)"
βββββββββββββββββ βββββββββββββββββ
β Call Stack β β Web API β
β β β β
β setTimeout() β β Timer: 0ms β
β global β β callback: () β
βββββββββββββββββ βββββββββββββββββ
(Timer starts in Web API)
Step 3: Execute "console.log('2. End')"
βββββββββββββββββ βββββββββββββββββ
β Call Stack β β Task Queue β
β β β β
β console.log() β β [callback] β β Timer finished
β global β β β
βββββββββββββββββ βββββββββββββββββ
Output: "2. End"
Step 4: Call stack empty, event loop moves callback
βββββββββββββββββ βββββββββββββββββ
β Call Stack β β Task Queue β
β β β β
β callback β β (empty) β
β global β β β
βββββββββββββββββ βββββββββββββββββ
Output: "3. Timeout"
Macrotasks vs Microtasks
Macrotasks (Task Queue):
- β’
setTimeout - β’
setInterval - β’
setImmediate(Node.js) - β’I/O operations
- β’UI rendering events
Microtasks (Microtask Queue):
- β’
Promise.then/catch/finally - β’
queueMicrotask() - β’
MutationObserver
Priority: Microtasks Run First!
console.log('1. Start');
setTimeout(() => console.log('4. Timeout'), 0);
Promise.resolve().then(() => console.log('3. Promise'));
console.log('2. End');
// Output: 1. Start, 2. End, 3. Promise, 4. Timeout
Why?
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β EXECUTION ORDER β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β 1. Sync code runs first (call stack) β
β β "1. Start" β
β β setTimeout callback β Task Queue β
β β Promise callback β Microtask Queue β
β β "2. End" β
β β
β 2. Call stack empty β Check microtasks β
β β Run ALL microtasks β
β β "3. Promise" β
β β
β 3. Microtasks empty β Run ONE macrotask β
β β "4. Timeout" β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Nested Microtasks
Microtasks can add more microtasksβall run before the next macrotask:
console.log('1. Start');
setTimeout(() => console.log('5. Timeout'), 0);
Promise.resolve()
.then(() => {
console.log('2. Promise 1');
return Promise.resolve();
})
.then(() => {
console.log('3. Promise 2');
});
Promise.resolve().then(() => console.log('4. Promise 3'));
// Output: 1. Start, 2. Promise 1, 4. Promise 3, 3. Promise 2, 5. Timeout
Common Patterns
Deferring Execution:
// Run after current sync code
setTimeout(() => {
console.log('Deferred');
}, 0);
// Higher priority deferring (microtask)
queueMicrotask(() => {
console.log('Microtask deferred');
});
Breaking Up Long Tasks:
// BAD: Blocks the main thread
function processLargeArray(array) {
for (let i = 0; i < array.length; i++) {
heavyComputation(array[i]);
}
}
// GOOD: Yields to event loop
function processLargeArrayAsync(array, index = 0) {
if (index >= array.length) return;
heavyComputation(array[index]);
// Schedule next iteration
setTimeout(() => {
processLargeArrayAsync(array, index + 1);
}, 0);
}
The Rendering Pipeline
In browsers, rendering happens between task queue processing:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FRAME LIFECYCLE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββ βββββββββββββββ ββββββββββββ βββββββββββ β
β β Task β β β Microtasks β β β Render β β β Task β β
β βββββββββββ βββββββββββββββ ββββββββββββ βββββββββββ β
β β
β Task: setTimeout, events, etc. β
β Microtasks: Promises, queueMicrotask β
β Render: requestAnimationFrame, style, layout, paint β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
requestAnimationFrame:
// Runs before next paint (smooth animations)
function animate() {
element.style.left = parseInt(element.style.left) + 1 + 'px';
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
Blocking the Event Loop
Long-running sync code blocks everything:
// DON'T DO THIS!
console.log('Start');
// Blocks for ~3 seconds
const end = Date.now() + 3000;
while (Date.now() < end) {
// Busy loop - blocks event loop!
}
console.log('End');
// UI is frozen during the while loop
Signs of Blocking:
- β’Unresponsive UI
- β’Animations freeze
- β’Click events delayed
- β’"Page unresponsive" warnings
Node.js Event Loop Phases
Node.js has a more detailed event loop:
βββββββββββββββββββββββββββββ
βββΊβ timers β β setTimeout, setInterval
β βββββββββββββββ¬ββββββββββββββ
β βββββββββββββββ΄ββββββββββββββ
β β pending callbacks β β I/O callbacks
β βββββββββββββββ¬ββββββββββββββ
β βββββββββββββββ΄ββββββββββββββ
β β idle, prepare β β internal only
β βββββββββββββββ¬ββββββββββββββ
β βββββββββββββββ΄ββββββββββββββ
β β poll β β new I/O events
β βββββββββββββββ¬ββββββββββββββ
β βββββββββββββββ΄ββββββββββββββ
β β check β β setImmediate
β βββββββββββββββ¬ββββββββββββββ
β βββββββββββββββ΄ββββββββββββββ
ββββ€ close callbacks β β socket.on('close', ...)
βββββββββββββββββββββββββββββ
process.nextTick vs setImmediate:
// process.nextTick - runs before the event loop continues
process.nextTick(() => console.log('nextTick'));
// setImmediate - runs in check phase (after I/O)
setImmediate(() => console.log('setImmediate'));
// Microtasks
Promise.resolve().then(() => console.log('promise'));
// Order: nextTick, promise, setImmediate
Summary
| Concept | Description |
|---|---|
| Event Loop | Moves callbacks from queue to stack |
| Call Stack | Where synchronous code executes |
| Task Queue | setTimeout, setInterval, I/O |
| Microtask Queue | Promises, queueMicrotask |
| Web APIs | Browser-provided async operations |
| Blocking | Long sync code freezes everything |
| Microtasks First | All microtasks run before next macrotask |
Key Rules to Remember
- β’Sync code runs first - always
- β’Microtasks before macrotasks - promises before timeouts
- β’ALL microtasks run before the next macrotask
- β’Don't block - keep sync code short
- β’setTimeout(fn, 0) doesn't mean "immediate" - it means "as soon as possible after current code"
Debugging Tips
- β’Use
console.logwith numbered messages to trace order - β’Chrome DevTools Performance tab shows event loop blocking
- β’Look for long-running sync operations
- β’Use
requestIdleCallbackfor non-urgent work
Next Steps
In the next section, we'll explore Memory Managementβhow JavaScript handles memory allocation and garbage collection.