Skip to main content

Command Palette

Search for a command to run...

Node.js Internals: Event Loop & Thread Pool Explained

Updated
β€’4 min read

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


1 views