Skip to main content

Command Palette

Search for a command to run...

Node.js Internals: Event Loop & Thread Pool Explained

Updated
•4 min read•View as Markdown

Understanding how Node.js works under the hood is key to writing efficient and scalable backend applications. Let’s break down the Event Loop and Thread Pool in a simple, structured way.

1. The Core Idea

JavaScript in Node.js runs on a single main thread. Yet, it can handle thousands of operations concurrently.

How?

👉 Thanks to libUV, which provides:

  • ✅ Event Loop

  • ✅ Thread Pool


2. How a Node.js Program Runs

When you execute a Node.js file, the flow looks like this:

The top-level code executes first, and only after that, Node.js starts handling async tasks using the event loop.


3. Event Loop

At its core, the event loop is just a loop:

while (true) {
  1. Timers Phase
  2. I/O Polling Phase
  3. setImmediate Phase
  4. Close Callbacks Phase
}

👉 It keeps running as long as there are pending tasks.


4. Thread Pool Explained

The Thread Pool is a group of background worker threads.

🔹 It handles "CPU Intensive Operations":

  • File system operations (fs)

  • Cryptography

  • DNS lookups

  • Compression / Encryption

🔹 Default size:

4 threads

🔹 You can increase it:

process.env.UV_THREADPOOL_SIZE = 8;

👉 Important: The thread pool is mainly used for CPU-intensive or blocking tasks.


5. Event Loop Phases (Detailed)

🟡 1. Timers Phase

Executes:

setTimeout()
setInterval()

Example:

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

👉 Runs only after the timer expires, not immediately.


🟢 2. I/O Polling Phase (Most Important)

Executes:

  • I/O callbacks (like file reading)

Example:

fs.readFile('file.txt', () => {
  console.log("File Reading Completed");
});

👉 Responsibilities:

  • Execute completed I/O tasks

  • Decide what phase comes next


🟣 3. setImmediate Phase

Executes:

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

👉 Runs right after the I/O polling phase


🔴 4. Close Callbacks Phase

Executes cleanup logic:

socket.on('close', () => {});

6. setTimeout(0) vs setImmediate()

These two often confuse developers.

Function When it Runs
setTimeout(fn, 0) Next Timers phase
setImmediate(fn) After I/O Polling phase finishes

👉 setTimeout(fn, 0) is not truly immediate


7. Behavior Inside I/O

fs.readFile('file.txt', () => {
  setTimeout(() => console.log("Timer"), 0);
  setImmediate(() => console.log("Immediate"));
});

✅ Output:

Immediate
Timer

Why?

  • I/O completes → goes to setImmediate phase

  • Timer runs in the next loop iteration


8. Behavior Outside I/O (Unpredictable)

setTimeout(() => console.log("Timer"), 0);
setImmediate(() => console.log("Immediate"));

❓ Output:

Not fixed

👉 Depends on:

  • System performance

  • Execution timing

  • Event loop start timing


9. Top-Level Code Matters

setTimeout(() => console.log("Timer"), 0);
setImmediate(() => console.log("Immediate"));

console.log("Top Level");

Output:

Top Level
Timer
Immediate

👉 Because:

  • Top-level code delays event loop start

  • Timer gets time to expire first


Without Top-Level Code:

setTimeout(() => console.log("Timer"), 0);
setImmediate(() => console.log("Immediate"));

👉 Event loop starts immediately 👉 Timer may not be ready yet

Possible Output:

Immediate
Timer

10. process.nextTick()

This is not part of the event loop.

Definition:

👉 Executes right after current operation, before event loop starts.

console.log("Start");

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

console.log("End");

Output:

Start
End
Next Tick

11. Final Execution Priority

1. Top-level code
2. process.nextTick()
3. Event Loop starts:
   → Timers
   → I/O Polling
   → setImmediate
   → Close Callbacks

Final Thoughts

  • Node.js is single-threaded, but not limited

  • The event loop enables non-blocking execution

  • The thread pool handles heavy tasks in the background

  • Understanding phase order is critical for debugging async behavior


2 views