Node.js Event Loop Phases — Complete Guide to libuv Internals
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
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