Skip to main content

Command Palette

Search for a command to run...

Node.js Event Loop Explained

Updated
•8 min read•View as Markdown

Node.js is an open-source and cross-platform JavaScript runtime environment. It is a popular tool for almost any kind of project. it is famous for being fast and scalable, but many developers struggle to understand how it handles multiple tasks with a single thread.

The answer is the Event Loop.

In this article, we will understand:

  • What the Event Loop is

  • How Node.js handles asynchronous tasks

  • Event Loop phases

  • setTimeout() vs setImmediate()

  • process.nextTick()

  • Real-world project examples

What is the Node.js Event Loop?

The Event Loop is the mechanism that allows Node.js to perform non-blocking operations using a single thread.Normally, JavaScript runs one task at a time, but Node.js can handle thousands of requests simultaneously.

How? Because heavy operations like:

  • File reading

  • Network requests

  • Database queries

are handled by the system kernel or background threads, while Node.js continues executing other code.Once the background task finishes, the Event Loop executes the callback.

Simple Way

Request comes → Background work starts → Event loop waits
Work finishes → Callback added to queue → Event loop executes it

Imagine a restaurant waiter.

The waiter:

  1. Takes an order

  2. Sends it to the kitchen

  3. Takes another order from another customer

  4. Delivers food when ready

The waiter doesn't wait near the kitchen for one order.

Similarly, Node.js doesn't block execution while waiting for operations.

Event Loop Phases

The Event Loop runs in multiple phases.

timers
pending callbacks
idle / prepare
poll
check
close callbacks

Each phase has its own queue of callbacks.

The event loop runs these phases continuously.

┌───────────────────────────┐
│           timers          │
├───────────────────────────┤
│     pending callbacks     │
├───────────────────────────┤
│       idle, prepare       │
├───────────────────────────┤
│           poll            │
├───────────────────────────┤
│           check           │
├───────────────────────────┤
│      close callbacks      │
└───────────────────────────┘

1.Timers Phase

The timers phase executes callbacks scheduled using:

  • setTimeout()

  • setInterval()

These functions allow developers to schedule code to run after a specified minimum delay.
The delay provided to setTimeout() or setInterval() is not guaranteed to be exact. It only specifies the minimum time before the callback can run. If the event loop is busy, the callback may run later.

setTimeout(() => {
  console.log("Executed after at least 100ms");
}, 100);

In this example, Node.js schedules the callback to run after at least 100 milliseconds, but the actual execution time may be slightly longer depending on the event loop workload.

2.Pending Callbacks Phase

The pending callbacks phase executes certain I/O callbacks that were postponed to the next iteration of the event loop. These callbacks usually come from system-level operations such as:

  • TCP connection errors

  • Network failures

  • Some low-level system errors

These operations cannot always be handled immediately, so Node.js defers them to the next event loop cycle, and they are executed during the pending callbacks phase.

Imagine a server trying to connect to another server.

const net = require("net");

const client = net.createConnection({ port: 3000 });

client.on("error", (err) => {
  console.log("Connection error:", err.code);
});

If the connection fails with an error like ECONNREFUSED, Node.js may delay handling that error and process it in the pending callbacks phase of the next event loop iteration.

3.Idle, Prepare Phase

The idle and prepare phases are internal phases used by Node.js and the libuv library.

They are not directly accessible to developers and are mainly used for internal bookkeeping and preparation work before the poll phase begins.

During these phases, Node.js may:

  • Prepare internal data structures

  • Perform internal checks

  • Set up the environment before polling for new I/O events

Since these phases are handled internally by Node.js, developers usually do not interact with them directly.

4.Poll Phase

The poll phase is the most important phase of the event loop. Its main responsibilities are:

  1. Retrieve new I/O events

  2. Execute I/O-related callbacks

These I/O operations include:

  • File system operations (fs.readFile)

  • Database responses

  • API responses

  • Network requests

const fs = require("fs");

fs.readFile("data.txt", () => {
  console.log("File reading completed");
});

In this example:

  1. Node.js sends the file reading request to the operating system.

  2. When the file reading completes, the callback is added to the poll queue.

  3. The event loop executes it during the poll phase.

If there are no callbacks in the poll queue, Node.js may wait (block) for new I/O events to arrive. This waiting behavior is what allows Node.js to be efficient and non-blocking.

5.Check Phase

The check phase executes callbacks scheduled using:

setImmediate() schedules a function to run immediately after the poll phase completes.

setImmediate(() => {
  console.log("Executed in check phase");
});

This callback will run after the poll phase of the event loop.

6.Close Callbacks Phase

The close callbacks phase executes callbacks related to closed resources, such as sockets or handles. For example, when a socket connection closes unexpectedly, the associated callback runs in this phase.

socket.on("close", () => {
  console.log("Socket connection closed");
});

This callback will run in the close callbacks phase when the socket is closed.

Understanding Async Execution in Node.js

Node.js has multiple ways to schedule asynchronous code. The most common ones are:

  • setTimeout()

  • setImmediate()

  • process.nextTick()

  • Promise.then()

  • queueMicrotask()

Although they look similar, they run in different queues and different phases of the Event Loop. Understanding their execution order is very important for Node.js interviews and debugging asynchronous code.

1. setTimeout() :-

setTimeout() schedules a callback to run after a minimum delay (in milliseconds). The callback is placed in the Timers phase of the Event Loop.
The delay is not exact. It only guarantees that the callback will not execute before the specified time.

  • Run the callback after at least 100ms . Execution may happen later if the event loop is busy.

  • Event Loop Phase

Timers Phase
  • Delayed tasks

  • Retry logic

  • Debouncing operations

  • Scheduling background tasks

setTimeout(() => {
  console.log("Retrying API request...");
}, 2000);

2. setImmediate()

setImmediate() schedules a callback to run immediately after the poll phase finishes. The callback runs in the Check phase of the Event Loop.

setImmediate(() => {
  console.log("Immediate executed");
});

Event Loop Phase

Check Phase
  • Running code right after I/O operations

  • Breaking long CPU tasks into smaller chunks

  • Avoiding blocking the event loop

const fs = require("fs");

fs.readFile("file.txt", () => {

  setImmediate(() => {
    console.log("Processing file after reading");
  });

});

3. process.nextTick()

process.nextTick() schedules a callback to execute immediately after the current operation completes, before the event loop continues. It does not belong to any event loop phase. Instead, it runs in the Next Tick Queue, which has higher priority than all other queues.

console.log("Start");

process.nextTick(() => {
  console.log("Next Tick");
});

console.log("End");
// output is
Start
End
Next Tick
  • Error handling

  • Ensuring asynchronous behavior

  • Running callbacks after the current stack finishes

Example

function validate(data, callback) {

  if (!data) {
    return process.nextTick(() =>
      callback(new Error("Invalid data"))
    );
  }

}

4. Promises (Microtasks)

Promises schedule callbacks using the Microtask Queue. Promise callbacks run after the current synchronous code finishes, but before the event loop continues.

console.log("Start");

Promise.resolve().then(() => {
  console.log("Promise executed");
});

console.log("End");
// Output is 
Start
End
Promise executed

Event Loop Queue

Microtask Queue
  • Async operations

  • API requests

  • Database queries

  • Modern asynchronous code using async/await

async function getUsers() {
  const users = await database.getUsers();
  return users;
}

5. queueMicrotask()

queueMicrotask() schedules a function in the microtask queue, similar to Promises. It allows developers to manually schedule microtasks.

queueMicrotask(() => {
  console.log("Microtask executed");
});

Event Loop Queue

Microtask Queue
  • Framework internals

  • Scheduling lightweight async tasks

  • Similar behavior to Promise microtasks

queueMicrotask(() => {
  updateUI();
});

6. Execution Order

console.log("Start");

setTimeout(() => {
  console.log("setTimeout");
}, 0);

setImmediate(() => {
  console.log("setImmediate");
});

process.nextTick(() => {
  console.log("nextTick");
});

Promise.resolve().then(() => {
  console.log("promise");
});

queueMicrotask(() => {
  console.log("queueMicrotask");
});

console.log("End");
Start
End
nextTick
promise
queueMicrotask
setTimeout / setImmediate (depends on context)

7. Execution Priority Diagram

1️⃣ Synchronous Code
       ↓
2️⃣ process.nextTick()
       ↓
3️⃣ Promise.then()
       ↓
4️⃣ queueMicrotask()
       ↓
5️⃣ Event Loop Phases
     ├─ Timers (setTimeout)
     ├─ Poll (I/O)
     ├─ Check (setImmediate)
     └─ Close callbacks

8.Inside an I/O operation:

const fs = require("fs");

fs.readFile(__filename, () => {

  setTimeout(() => console.log("timeout"), 0);

  setImmediate(() => console.log("immediate"));

});
// output is 
immediate
timeout
  • Poll phase finishes

  • Check phase runs (setImmediate)

  • Timers phase runs later

9. Quick Comparison Table

Method Queue / Phase Execution Time
process.nextTick Next Tick Queue Immediately after current operation
Promise.then Microtask Queue After nextTick but before event loop
queueMicrotask Microtask Queue Same as Promise microtasks
setTimeout Timers Phase After minimum delay
setImmediate Check Phase After poll phase

Important facts interviewers expect:

1️⃣ process.nextTick() runs before Promises
2️⃣ Promises run before event loop phases
3️⃣ setImmediate() runs in check phase
4️⃣ setTimeout() runs in timers phase
5️⃣ setImmediate() usually runs before timeout inside I/O