Back to 20 Concepts
event-loopAdvanced

The 6-Phase Libuv Event Loop (Timers, Poll, Check)

Each tick of the Libuv Event Loop processes 6 distinct phases: Timers -> Pending Callbacks -> Idle/Prepare -> Poll -> Check -> Close Callbacks.

Intuitive Mental Model

The Clock Face with 6 Stations: The train visits Station 1 (Timers: setTimeout), Station 2 (I/O Errors), Station 3 (Internal), Station 4 (Poll: incoming network/disk events), Station 5 (Check: setImmediate), and Station 6 (Close: socket.on("close")).

Node.js ESM / CJS ImplementationNode.js v22 LTS
import fs from 'fs';

// Timers Phase:
setTimeout(() => console.log('1. setTimeout (Timers Phase)'), 0);

// Check Phase:
setImmediate(() => console.log('2. setImmediate (Check Phase)'));

// Poll Phase (I/O):
fs.readFile('./package.json', () => {
  console.log('3. I/O Callback (Poll Phase)');
  // Inside I/O cycle, setImmediate ALWAYS executes before setTimeout!
  setTimeout(() => console.log('5. setTimeout in I/O'), 0);
  setImmediate(() => console.log('4. setImmediate in I/O'));
});

Key Architectural Takeaways

  • 1. Timers: Executes callbacks scheduled by setTimeout() and setInterval().
  • 2. Poll: Retrieves new I/O events (network packets, file reads) and executes their callbacks. Blocks if no other phases are queued.
  • 3. Check: Executes setImmediate() callbacks immediately after the Poll phase.
  • Inside an I/O callback, setImmediate is guaranteed to run before setTimeout(..., 0).
Common Production Mistake

Assuming setTimeout(fn, 0) runs in 0ms; operating systems enforce a minimum 1ms timer threshold.

Recommended Solution

Use setImmediate() when you want a callback to execute immediately after the current Poll cycle.