♻️
Node.js Event Loop
advancedNode.js uses libuv to implement its event loop, which has 6 distinct phases. Understanding these phases helps you predict execution order and avoid blocking the server.
🎮Interactive Visualization
Codesync
1setTimeout(() => {2 console.log('timeout');3}, 0);45setImmediate(() => {6 console.log('immediate');7});89console.log('sync');
process.nextTick
(empty)
Promise Queue
(empty)
Timers
-
Pending
-
Poll
-
Check
-
Close
-
Step 1/6Script starts. Node.js event loop initializes.
Key Insight: In main module, setTimeout vs setImmediate order varies! Inside I/O callbacks, setImmediate always runs first.
Key Points
- Node.js event loop has 6 phases (not just micro/macro queues)
- Timers phase: executes setTimeout/setInterval callbacks
- Poll phase: retrieves new I/O events, executes I/O callbacks
- Check phase: executes setImmediate callbacks
- process.nextTick runs BETWEEN phases (highest priority)
- Blocking the event loop freezes your entire server
💻Code Examples
Event Loop Phases
// Phase 1: Timers (setTimeout)
setTimeout(() => console.log("timer"), 0);
// Phase 4: Poll (I/O callbacks)
fs.readFile("file.txt", () => {
console.log("file read");
});
// Phase 5: Check (setImmediate)
setImmediate(() => console.log("immediate"));
// Between phases: process.nextTick
process.nextTick(() => console.log("nextTick"));
// Output order depends on I/O!Different callbacks run in different phases
setTimeout vs setImmediate
// In main module: order varies!
setTimeout(() => console.log("timeout"), 0);
setImmediate(() => console.log("immediate"));
// Inside I/O callback: immediate first!
fs.readFile("file.txt", () => {
setTimeout(() => console.log("timeout"), 0);
setImmediate(() => console.log("immediate"));
});
// Output: immediate, timeout
// (Check phase runs before Timers)setImmediate runs before setTimeout in I/O callbacks
process.nextTick
// nextTick runs BETWEEN phases
// (even before Promises!)
Promise.resolve().then(() => {
console.log("promise");
});
process.nextTick(() => {
console.log("nextTick");
});
// Output: nextTick, promise
// nextTick has highest priority!process.nextTick runs before Promise callbacks
nextTick Starvation
// DANGER: Recursive nextTick
// blocks the event loop forever!
function recurse() {
process.nextTick(recurse);
}
recurse();
// I/O callbacks will NEVER run
// Server becomes unresponsive!
// Fix: use setImmediate instead
function safeRecurse() {
setImmediate(safeRecurse);
}nextTick can starve I/O if abused
Blocking the Event Loop
// BAD: Blocks entire server!
app.get("/slow", (req, res) => {
// 10 billion iterations
for (let i = 0; i < 1e10; i++) {}
res.send("done");
});
// GOOD: Use Worker Threads
const { Worker } = require("worker_threads");
app.get("/fast", (req, res) => {
const worker = new Worker("./heavy.js");
worker.on("message", () => res.send("done"));
});CPU-heavy work blocks all requests
Event Loop Monitoring
// Monitor event loop lag
const start = process.hrtime();
setImmediate(() => {
const [s, ns] = process.hrtime(start);
const lag = s * 1000 + ns / 1e6;
console.log(`Event loop lag: ${lag}ms`);
});
// High lag = event loop blocked
// Target: < 100ms for responsive serverMonitor lag to detect blocking issues
Common Mistakes
- Confusing browser event loop with Node.js event loop
- Using recursive process.nextTick (causes starvation)
- Blocking the event loop with CPU-intensive code
- Assuming setTimeout(fn, 0) runs immediately
Interview Tips
- Know all 6 phases of the libuv event loop
- Explain the difference between setImmediate and setTimeout
- Understand why process.nextTick exists and when to use it
- Know how to handle CPU-intensive tasks (Worker Threads)