The Node.js Event Loop

Know the Loop's Phases and You'll Know Why Your Algorithm Blocks

The Node.js Event Loop

A deep look at the six phases of the Node.js event loop — timers, pending callbacks, poll, check, close — and why a tight algorithmic loop stalls every I/O callback in your server.

5 min read Level 3/5 #dsa#nodejs#interview
What you'll learn
  • Name the six phases of the Node.js event loop in order
  • Explain where microtasks (Promise callbacks and queueMicrotask) fit in the loop
  • Identify why a synchronous algorithm that runs too long blocks I/O handling

The Node.js event loop is the engine that keeps a single-threaded process responsive while waiting on I/O. Every algorithm you run in Node competes with network sockets, timers, and file reads for that one thread — understanding the loop’s phases tells you exactly when your code will block and when it won’t.

The Six Phases

The loop visits six phases on every iteration (“tick”):

PhaseHandles
TimerssetTimeout and setInterval callbacks whose delay has expired
Pending callbacksI/O callbacks deferred from the previous iteration
Idle / prepareInternal Node bookkeeping (not user-visible)
PollRetrieve new I/O events; blocks here when the queue is empty
ChecksetImmediate callbacks
Close callbackssocket.on('close', …) and similar cleanup handlers

After every phase transition — and after every individual callback — Node drains the microtask queue: first all process.nextTick callbacks, then all resolved Promise .then / catch / finally handlers.

Microtasks Beat Macrotasks

// Run in Node to see the order for yourself
setTimeout(() => console.log('timeout'), 0);

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

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

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

console.log('sync');

// Output:
// sync
// nextTick
// promise 1
// promise 2
// timeout

process.nextTick runs before any Promise callback. Both run before the timer phase fires — even when the timer delay is 0.

Why a Tight Loop Blocks Everything

The poll phase can only retrieve new network data when Node is there. If your algorithm takes 400 ms of synchronous CPU work, the loop cannot visit the poll phase for those 400 ms. Every waiting HTTP request, every database response, every file read sits in limbo.

import { createServer } from 'node:http';

const server = createServer((_req, res) => {
  // Simulates an expensive synchronous algorithm
  const end = Date.now() + 400;
  while (Date.now() < end) { /* busy-wait */ }
  res.end('done');
});

server.listen(3000);
// Every request serialises; concurrent clients all wait in line.

Algorithm Choice Is an Event-Loop Choice

This is why the data-structure and algorithm choices you make in a Node service are architectural decisions, not just academic ones:

  • O(n log n) sort on 10 000 items — finishes in a millisecond, invisible to the loop.
  • O(n²) nested loop on 100 000 items — tens of seconds; every user feels it.
  • Recursive DFS on a deep graph — risks blowing the call stack and blocking the loop.

Knowing the loop makes the performance cost of a naive algorithm concrete, not theoretical.

Up Next

Learn how to measure the cost of synchronous work and how to break it into non-blocking chunks with setImmediate.

Sync vs Async Performance →