Skip to content

Node.js Event Loop Phases — Complete Guide to libuv Internals

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Node.js Event Loop Phases. We cover key concepts, practical examples, and best practices to help you master this topic.

Node.js event loop phases Process callbacks in a specific order: timers, pending I/O callbacks, idle/prepare, poll, check, and close callbacks, repeating indefinitely.

What You'll Learn

By the end of this tutorial, you'll identify each of the six event loop phases, understand what runs in each phase, predict callback execution order, and debug timing issues.

Why Phases Matter

Knowing event loop phases helps you control when code executes, avoid starvation, optimize I/O handling, and understand why setTimeout behaves differently than setImmediate.

Real-World Use

An HTTP server that processes file uploads must handle timer callbacks after the poll phase completes. setImmediate guarantees execution after I/O, while setTimeout(fn, 0) depends on timer phase timing.

Event Loop Learning Path

flowchart LR
  A[Architecture] --> B[Event Loop Phases]
  B --> C[Timers]
  B --> D[setImmediate]
  B --> E[nextTick]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

Six Phases Overview

The event loop iterates through six phases: timers (expired callbacks), pending callbacks (I/O errors/polls), idle/prepare (internal), poll (I/O events), check (setImmediate), close (close events).

// This diagram shows event loop order
console.log("Phase order:");
console.log("1. timers: setTimeout, setInterval");
console.log("2. pending: I/O callbacks deferred");
console.log("3. idle, prepare: internal use");
console.log("4. poll: retrieve new I/O events");
console.log("5. check: setImmediate callbacks");
console.log("6. close: close event callbacks");

Timers Phase

The timers phase executes callbacks scheduled by setTimeout and setInterval whose thresholds have elapsed.

const start = Date.now();
setTimeout(() => {
  console.log("Timer 1 executed after", Date.now() - start, "ms");
}, 50);
setTimeout(() => {
  console.log("Timer 2 executed after", Date.now() - start, "ms");
}, 50);
// Both timers execute in the same phase if their time has elapsed

Poll Phase

The poll phase retrieves new I/O events and executes their callbacks. If no timers are pending, it blocks and waits for I/O.

const fs = require("node:fs");
const path = require("node:path");
const filePath = path.join(__dirname, "temp.txt");
fs.writeFileSync(filePath, "test data");
fs.readFile(filePath, "utf8", (err, data) => {
  console.log("Poll phase: file read complete");
});
setTimeout(() => {
  console.log("Timers phase: timeout executed");
  fs.unlinkSync(filePath);
}, 0);
// Output order: Poll phase: file read complete -> Timers phase: timeout executed

Check Phase

The check phase runs setImmediate callbacks. This phase runs immediately after the poll phase.

setImmediate(() => {
  console.log("Check phase: setImmediate 1");
});
setImmediate(() => {
  console.log("Check phase: setImmediate 2");
});
setTimeout(() => {
  console.log("Timers phase: timeout");
}, 0);
// When not in I/O cycle, order depends on phase timing

Close Callbacks Phase

The close callbacks phase handles cleanup events like socket.destroy() or stream.close().

const { Readable } = require("node:stream");
const readable = new Readable({
  read() {
    this.push(null);
  },
});
readable.on("close", () => {
  console.log("Close phase: stream closed");
});
readable.destroy();
setImmediate(() => {
  console.log("Check phase: after destroy");
});

Common Mistakes

1. Assuming setTimeout(fn, 0) Runs Before setImmediate

In the main module, timeout delay may cause setImmediate to run first. Inside I/O callbacks, setImmediate always runs before timers.

2. Blocking the Poll Phase

Synchronous operations in poll phase callbacks delay the entire loop. Keep callbacks short.

3. Not Understanding Phase Starvation

Recursive setImmediate calls starve the timers phase. Use setTimeout for delay-sensitive operations.

4. Forgetting the Pending Callbacks Phase

Some I/O callbacks (like TCP errors) are deferred to the pending phase. They run after timers but before poll.

5. Expecting Precise Timer Execution

Timers are approximate. The timers phase only runs when the loop reaches it, not immediately when time elapses.

Practice Questions

1. What are the six event loop phases in order?

Timers, pending callbacks, idle/prepare, poll, check, close callbacks.

2. In which phase does setImmediate run?

The check phase, which runs immediately after the poll phase.

3. What happens in the poll phase?

It retrieves new I/O events and executes their callbacks. If no timers are pending, it blocks waiting for I/O.

4. Why might setTimeout(fn, 0) not run before setImmediate?

In the main module, the timers phase has already passed. The first phase encountered is poll or check depending on timing.

5. Challenge: Prove that setImmediate runs before setTimeout inside an I/O callback.

const fs = require("node:fs");
fs.readFile(__filename, () => {
  setTimeout(() => console.log("timeout"));
  setImmediate(() => console.log("immediate"));
});
// Output: immediate then timeout

FAQ

What is the idle/prepare phase used for?

Internal libuv operations. Not accessible from JavaScript. Used for preparing the poll phase.

Can I block the event loop indefinitely?

Yes. A while(true) loop or synchronous operation in any phase blocks all phases.

How does process.nextTick relate to phases?

nextTick runs between phases, not within them. It can starve I/O if called recursively.

Does each iteration of the loop run all phases?

Not always. If the loop is in the poll phase and no I/O is pending, it may skip to timers.

What phase handles microtasks?

Microtasks (Promise.then) run after each callback in every phase, before moving to the next phase.

Mini Project: Phase Visualizer

Build a script that demonstrates event loop phase ordering with labeled outputs.

const fs = require("node:fs");
fs.readFile(__filename, "utf8", () => {
  setTimeout(() => console.log("1. Timers phase"));
  setImmediate(() => console.log("2. Check phase"));
  process.nextTick(() => console.log("0. nextTick between phases"));
  Promise.resolve().then(() => console.log("0. Promise microtask between phases"));
});

What's Next

Node.js Timers and setImmediate Node.js process.nextTick Node.js Blocking vs Non-Blocking

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro