How the Event Loop Works
The event loop is the core mechanism that enables JavaScript’s asynchronous behavior despite being single-threaded. It continuously monitors the call stack and queues, moving callbacks to the stack when it’s empty.
Event Loop Algorithm
Section titled “Event Loop Algorithm”flowchart TD Start([Start Event Loop]) --> CheckStack{Call Stack Empty?} CheckStack -->|No| Wait[Wait for stack to empty] Wait --> CheckStack
CheckStack -->|Yes| ProcessMicro[Process ALL Microtasks] ProcessMicro --> MicroExists{More Microtasks?} MicroExists -->|Yes| ProcessMicro MicroExists -->|No| ProcessMacro[Process ONE Macrotask]
ProcessMacro --> MacroExists{Macrotask Available?} MacroExists -->|Yes| Execute[Execute Macrotask] Execute --> CheckStack
MacroExists -->|No| Render{Need Render?} Render -->|Yes| RenderFrame[Render Frame] RenderFrame --> CheckStack Render -->|No| CheckStackSimple Event Loop Simulation
Section titled “Simple Event Loop Simulation”// Simulating the event loopclass SimpleEventLoop { constructor() { this.microtasks = []; this.macrotasks = []; this.running = true; }
// Add microtask queueMicrotask(callback) { this.microtasks.push(callback); }
// Add macrotask queueMacrotask(callback) { this.macrotasks.push(callback); }
// Run the event loop async run() { while (this.running) { // Step 1: Process all microtasks while (this.microtasks.length > 0) { const microtask = this.microtasks.shift(); try { microtask(); } catch (error) { console.error("Microtask error:", error); } }
// Step 2: Process one macrotask if (this.macrotasks.length > 0) { const macrotask = this.macrotasks.shift(); try { macrotask(); } catch (error) { console.error("Macrotask error:", error); } }
// Step 3: Allow for rendering (simulated) await this.delay(0); } }
delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }
stop() { this.running = false; }}
// Demonstrationconst loop = new SimpleEventLoop();
loop.queueMacrotask(() => console.log("Macrotask 1"));loop.queueMicrotask(() => console.log("Microtask 1"));loop.queueMacrotask(() => console.log("Macrotask 2"));loop.queueMicrotask(() => console.log("Microtask 2"));loop.queueMacrotask(() => console.log("Macrotask 3"));
console.log("Event loop starting...");// loop.run();
// Expected output order:// Microtask 1// Microtask 2// Macrotask 1// Macrotask 2// Macrotask 3Real Event Loop Examples
Section titled “Real Event Loop Examples”console.log("=== Real Event Loop Examples ===");
// Example 1: Basic event loop flowfunction eventLoopExample1() { console.log("A: Start - Call Stack: [global]");
setTimeout(() => { console.log("D: Timeout - Macrotask Queue"); }, 0);
Promise.resolve().then(() => { console.log("C: Promise - Microtask Queue"); });
console.log("B: End - Call Stack: [global]");}
eventLoopExample1();// Output: A, B, C, D
// Example 2: Nested asynchronous operationsfunction eventLoopExample2() { console.log("1: Start");
setTimeout(() => { console.log("2: Timeout 1");
Promise.resolve().then(() => { console.log("3: Promise inside timeout"); });
setTimeout(() => { console.log("4: Timeout inside timeout"); }, 0); }, 0);
Promise.resolve().then(() => { console.log("5: Promise 1");
queueMicrotask(() => { console.log("6: Microtask inside promise"); }); });
console.log("7: End");}
eventLoopExample2();// Output: 1, 7, 5, 6, 2, 3, 4
// Example 3: Event loop with I/O (Node.js)const fs = require("fs");
console.log("Start");
fs.readFile(__filename, () => { console.log("I/O Callback (Poll phase)");
setImmediate(() => { console.log("setImmediate (Check phase)"); });
setTimeout(() => { console.log("setTimeout (Timers phase)"); }, 0);
Promise.resolve().then(() => { console.log("Promise (Microtask)"); });});
setTimeout(() => { console.log("Top-level setTimeout");}, 0);
setImmediate(() => { console.log("Top-level setImmediate");});
console.log("End");
// Output order may vary but microtasks always first:// Start, End, Top-level setTimeout/setImmediate (order varies),// I/O Callback, Promise, setImmediate, setTimeoutEvent Loop Visual Debugging
Section titled “Event Loop Visual Debugging”// Visualizing event loop with timestampsfunction debugEventLoop(label) { const timestamp = performance.now().toFixed(2); console.log(`[${timestamp}ms] ${label}`);}
// Track event loop iterationslet iteration = 0;const interval = setInterval(() => { iteration++; debugEventLoop(`Event loop iteration ${iteration} start`);
// Force microtask Promise.resolve().then(() => { debugEventLoop(` Microtask in iteration ${iteration}`); });
debugEventLoop(`Event loop iteration ${iteration} end`);}, 100);
setTimeout(() => { clearInterval(interval); debugEventLoop("Monitoring stopped");}, 1000);
// Detect event loop lagfunction detectLag() { let lastCheck = Date.now();
setInterval(() => { const now = Date.now(); const lag = now - lastCheck - 100; // Subtract expected interval
if (lag > 5) { console.warn(`⚠️ Event loop lag detected: ${lag}ms`); }
lastCheck = now; }, 100);}
detectLag();Event Loop in Different Environments
Section titled “Event Loop in Different Environments”// Browser event loopif (typeof window !== "undefined") { console.log("Browser Event Loop");
// Rendering happens between tasks let frameCount = 0;
function measureBrowserLoop() { const start = performance.now();
setTimeout(() => { console.log("Macrotask completed"); }, 0);
Promise.resolve().then(() => { console.log("Microtask completed"); });
requestAnimationFrame(() => { const duration = performance.now() - start; console.log(`Frame rendered after ${duration.toFixed(2)}ms`); }); }}
// Node.js event loopif (typeof process !== "undefined") { console.log("Node.js Event Loop");
const { performance } = require("perf_hooks");
// Different phases demonstration setTimeout(() => console.log("Timers phase"), 0); setImmediate(() => console.log("Check phase")); process.nextTick(() => console.log("nextTick (microtask)"));
// Monitor loop health let lastCheck = performance.now();
setInterval(() => { const now = performance.now(); const delay = now - lastCheck - 1000;
if (delay > 10) { console.log(`Loop blocked for ${delay.toFixed(2)}ms`); }
lastCheck = now; }, 1000);}