Explain the Node.js event loop and its phases.

4 minadvancednodejsevent-looplibuvphasesconcurrency

Quick Answer

The event loop is libuv's mechanism for scheduling callbacks across repeated phases: timers (setTimeout/setInterval), pending callbacks, poll (I/O), check (setImmediate), and close callbacks. Between every phase Node drains the microtask queues (process.nextTick, then Promises).

Detailed Answer

Answer: The event loop is what lets a single-threaded runtime handle many operations concurrently. It runs in repeated iterations ("ticks"), each moving through ordered phases, running the callbacks queued for that phase.

Phases (in order):

  1. Timers — callbacks scheduled by setTimeout / setInterval whose time has elapsed.
  2. Pending callbacks — certain deferred system callbacks (e.g., some TCP errors).
  3. Idle/prepare — internal use.
  4. Poll — retrieve new I/O events and run their callbacks (reading files, sockets, etc.). If nothing is pending, it may block here waiting for I/O.
  5. ChecksetImmediate callbacks run here.
  6. Close callbacks — e.g., socket.on('close', ...).

Between phases: microtasks. After each phase (and after each macrotask callback), Node drains:

  1. the process.nextTick queue, then
  2. the Promise microtask queue. Microtasks always run before the loop proceeds to the next phase.
console.log('start');
setTimeout(() => console.log('timeout'), 0);   // timers phase
setImmediate(() => console.log('immediate'));  // check phase
Promise.resolve().then(() => console.log('promise')); // microtask
process.nextTick(() => console.log('nextTick')); // runs before promises
console.log('end');

// start, end, nextTick, promise, timeout, immediate

Key mental model: run synchronous code → drain microtasks → run one phase's callbacks → drain microtasks → next phase. Long synchronous work or a flood of nextTick callbacks can starve I/O.