Node.jsEvent Loop Deep Dive

The Node.js Event Loop

The event loop is what allows Node.js to perform non-blocking I/O operations despite JavaScript being single-threaded.

Phases of the Event Loop

The event loop has several phases, each with a FIFO queue of callbacks to execute:

  • Timers: Executes callbacks scheduled by setTimeout() and setInterval()
  • Pending Callbacks: Executes I/O callbacks deferred to the next loop iteration
  • Idle, Prepare: Used internally by Node.js
  • Poll: Retrieves new I/O events; executes I/O related callbacks
  • Check: setImmediate() callbacks are invoked here
  • Close Callbacks: Handles closed connections like socket.on('close', ...)
  • Understanding process.nextTick()

    process.nextTick() is not technically part of the event loop. It's processed after the current operation completes, before the event loop continues.

    console.log('Start');

    setTimeout(() => console.log('Timeout'), 0); setImmediate(() => console.log('Immediate'));

    process.nextTick(() => console.log('NextTick'));

    Promise.resolve().then(() => console.log('Promise'));

    console.log('End');

    // Output: // Start // End // NextTick // Promise // Timeout (or Immediate) // Immediate (or Timeout)

    Microtasks vs Macrotasks

  • Microtasks: process.nextTick, Promise.then, queueMicrotask
  • Macrotasks: setTimeout, setInterval, setImmediate, I/O
  • Microtasks are processed between each phase of the event loop.

    Avoiding Event Loop Blocking

    // Bad - blocks the event loop
    function processLargeArray(array) {
      array.forEach(item => heavyComputation(item));
    }

    // Good - yields to the event loop async function processLargeArrayAsync(array) { for (const item of array) { await heavyComputation(item); await new Promise(resolve => setImmediate(resolve)); } }