Ever wondered how Node.js handles thousands of concurrent connections with a single thread? The secret is the Event Loop.
The Phases
The Node.js event loop runs in 6 phases:
┌───────────────────────────┐
┌─>│ timers │ ← setTimeout, setInterval
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │ ← I/O callbacks deferred
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │ ← internal use only
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ poll │ ← retrieve new I/O events
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ check │ ← setImmediate() callbacks
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │ ← socket.on('close', ...)
└───────────────────────────┘
Key Insights
1. setTimeout vs setImmediate
// In I/O callback, setImmediate ALWAYS runs first fs.readFile('file.txt', () => { setTimeout(() => console.log('timeout'), 0); setImmediate(() => console.log('immediate')); }); // Output: immediate, timeout
2. process.nextTick() has priority
Promise.resolve().then(() => console.log('promise')); process.nextTick(() => console.log('nextTick')); // Output: nextTick, promise // nextTick runs BEFORE microtasks!
3. Blocking the Event Loop
// ❌ BAD - Blocks event loop const result = fs.readFileSync('huge.txt'); // ✅ GOOD - Non-blocking fs.readFile('huge.txt', (err, data) => { // Event loop continues while reading });
The Poll Phase
This is where Node.js spends most time:
- Executes I/O callbacks (file, network)
- Calculates how long to block and wait for I/O
- Only moves to next phase when:
- Poll queue is empty
- Or check phase has callbacks (setImmediate)
Why This Matters
Understanding the event loop helps you:
- Avoid blocking operations
- Debug timing issues
- Optimize async code
- Handle concurrency correctly
Pro tip: Use process.hrtime.bigint() to measure event loop lag in production!
const start = process.hrtime.bigint(); setImmediate(() => { const lag = process.hrtime.bigint() - start; console.log(`Event loop lag: ${lag / 1000000n}ms`); });