Spring Fresh Sale! - Up To 67% OFF BDIX Hosting + Free Domain
Node.js

Understanding the Node.js Event Loop: Phases, Microtasks, and Blocking Explained

The event loop is what lets single-threaded Node.js handle thousands of connections at once: instead of waiting on slow operations, it hands them off and keeps working, running your callbacks when the results are ready. Understanding its phases is the difference between an app that scales and one that mysteriously stalls.

The phases, in order

Each turn of the loop moves through phases, each with its own callback queue:

  • Timers — runs callbacks from setTimeout and setInterval.
  • Pending callbacks — certain deferred system callbacks.
  • Poll — retrieves new I/O events and runs their callbacks (the loop spends most time here).
  • Check — runs setImmediate callbacks.
  • Close — handles close events like a socket closing.

Where microtasks fit

Between operations, Node drains two microtask queues: the process.nextTick queue first, then the Promise queue. These run before the loop moves on, so a flood of process.nextTick calls can starve the loop. For the exact ordering, see nextTick vs setImmediate vs setTimeout.

The cardinal sin: blocking the loop

Because it is single-threaded, any long synchronous operation freezes everything — no other request is served until it finishes. Common offenders:

  • Heavy loops or large JSON.parse on huge payloads.
  • Synchronous file or crypto calls (the ...Sync versions).
  • CPU-bound work like image processing or complex calculations.

The fixes: use asynchronous APIs, break big jobs into chunks, and offload CPU-heavy work to worker threads.

Frequently asked questions

Is Node.js really single-threaded?

Your JavaScript runs on one main thread, but Node uses a background thread pool (libuv) for I/O. So your code is single-threaded while I/O happens in parallel underneath — which is why blocking that one thread is so damaging.

How do I know if I'm blocking the event loop?

Symptoms include requests hanging under load and rising latency. You can measure event-loop lag with monitoring tools — see monitoring a production Node.js app.

Should I use setImmediate or setTimeout(fn, 0)?

Prefer setImmediate when you want to yield after the current poll phase. Their exact ordering differs by context — see the dedicated timing guide.

Was this article helpful?