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:

  1. Timers Phase

    • Executes callbacks from setTimeout() and setInterval() whose time has expired.
  2. Pending Callbacks Phase

    • Executes I/O callbacks that were deferred to the next loop iteration.
  3. Idle, Prepare Phase

    • Internal use by Node.js.
  4. Poll Phase

    • Retrieves new I/O events.

    • Executes almost all I/O-related callbacks (like reading from files or sockets).

  5. Check Phase

    • Executes setImmediate() callbacks.
  6. Close Callbacks Phase

    • Handles cleanup for closed resources (like sockets).
  7. 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

  1. Node.js is single-threaded but non-blocking because of the event loop.

  2. Timers (setTimeout) and setImmediate are scheduled in different phases.

  3. process.nextTick() runs before microtasks and next event loop phases.

  4. Promises are part of the microtask queue — always run after the current operation but before the next phase.

  5. 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