Back to Notes
til
nodejs
event-loop
javascript
backend

Node.js Event Loop: How It Really Works

November 28, 2025
Share:TwitterLinkedIn

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`); });
Share:TwitterLinkedIn