Let’s do a deep dive into the Node.js event loop, because understanding it is key to mastering asynchronous behavior, performance, and non-blocking I/O. We’ll break it down carefully.
1️⃣ What is the Event Loop?
-
Node.js is single-threaded but handles asynchronous operations efficiently.
-
The event loop is the mechanism that processes events, callbacks, and I/O tasks.
-
Allows Node.js to do non-blocking operations while waiting for I/O.
Think of it as a task manager: it keeps checking what’s ready to execute and handles it in order.
2️⃣ Event Loop Phases
Node.js uses libuv under the hood and has several phases:
-
Timers Phase
- Executes callbacks from
setTimeout()
andsetInterval()
whose time has expired.
- Executes callbacks from
-
Pending Callbacks Phase
- Executes I/O callbacks that were deferred to the next loop iteration.
-
Idle, Prepare Phase
- Internal use by Node.js.
-
Poll Phase
-
Retrieves new I/O events.
-
Executes almost all I/O-related callbacks (like reading from files or sockets).
-
-
Check Phase
- Executes
setImmediate()
callbacks.
- Executes
-
Close Callbacks Phase
- Handles cleanup for closed resources (like sockets).
-
Microtasks (process.nextTick & Promises)
-
process.nextTick()
callbacks run before anything else in the current loop. -
Promises
.then()
/.catch()
run in the microtask queue, after the current operation but before the next phase.
-
3️⃣ Execution Order Example
console.log("Start");
setTimeout(() => console.log("Timeout 0ms"), 0);
setImmediate(() => console.log("Immediate"));
process.nextTick(() => console.log("Next Tick"));
Promise.resolve().then(() => console.log("Promise"));
console.log("End");
Output Explained
Start
End
Next Tick // process.nextTick runs first
Promise // microtasks queue (Promises) run next
Timeout 0ms // timers phase
Immediate // check phase
process.nextTick()
> Promises > timers > I/O > setImmediate
4️⃣ Key Takeaways
-
Node.js is single-threaded but non-blocking because of the event loop.
-
Timers (
setTimeout
) and setImmediate are scheduled in different phases. -
process.nextTick()
runs before microtasks and next event loop phases. -
Promises are part of the microtask queue — always run after the current operation but before the next phase.
-
I/O-heavy apps benefit from understanding which phase executes which callbacks to optimize performance.
5️⃣ Practical Implications
-
Avoid blocking the event loop (no heavy computations in main thread).
-
Use async I/O (fs.promises, database calls) for scalability.
-
Timers may not run exactly at the scheduled time; they run in the timers phase of the loop.
-
Mixing setTimeout, setImmediate, and process.nextTick can affect execution order and performance.
6️⃣ Visual Summary
Call Stack -> Event Loop -> Phases:
Timers -> I/O Callbacks -> setImmediate -> Close Callbacks
Microtasks: process.nextTick -> Promises