Sitemap

Understanding Processes and Threads in Node.js: A Deep Dive Into Concurrency and Parallelism

4 min readApr 19, 2025

--

Press enter or click to view image in full size

If you’re a Node.js developer working with CPU-intensive or I/O-heavy tasks, chances are you’ve heard advice like: “Offload heavy computation to a worker thread” or “Spawn separate processes for isolation.” But what does that really mean? To get the most out of Node.js, it’s crucial to understand how processes and threads work under the hood — and how they interact with your code.

In this post, we’ll break down the difference between processes and threads, explore Node’s internal thread model, and walk through some hands-on examples to see them in action.

What Is a Process?

A process is a running instance of a program. In Node.js, every time you run a .js file using the node command, you create a process. Each process:

  • Has its own memory space
  • Contains a single instruction pointer
  • Can only run one task at a time, unless it spawns threads or other processes

Let’s test this with a simple infinite loop:

// process.js
const process_name = process.argv.slice(2)[0];

let count = 0;
while (true) {
count++;
if (count === 2000 || count === 4000) {
console.log(`${process_name}: ${count}`);
}
}

Save this as process.js, then run it:

node process.js A &

The & sends the process to the background. You’ll see output like:

[1] 7754
A: 2000
A: 4000

Here, 7754 is the process ID (PID). You can verify it's running with:

ps | grep node

Multiple Processes: Concurrent, Not Parallel

Now run three more versions:

node process.js B & node process.js C & node process.js D &

Each one is a separate process. Their outputs might appear in any order:

D: 2000
D: 4000
B: 2000
B: 4000
C: 2000
C: 4000

Why the randomness? Because the operating system schedules processes based on various algorithms. On a single-core system, this means context-switching between them. On a multi-core system, they may run in parallel, with each core executing a separate process.

You can stop them all with:

sudo kill -9 `pgrep node`

This forcefully ends all Node processes.

What Are Threads?

Threads are like lightweight processes. They:

  • Share memory within a process
  • Have their own instruction pointer
  • Are more efficient to spawn than full processes

Node.js gives us access to threads via the worker_threads module. But even without explicitly using it, Node.js uses threads under the hood to handle I/O.

Hidden Threads in Node.js

Though JavaScript is single-threaded, Node.js is not. It uses the libuv library to manage background threads for non-blocking I/O operations. Every Node.js process has:

  • 1 main thread (your JS code)
  • 4 libuv threads (for I/O tasks like fs, DNS, and networking)
  • 2 V8 threads (for garbage collection and internal optimization)

Let’s confirm that:

node process.js A &

Note the process ID (e.g., 9933), then run:

top -H -p 9933

You’ll see something like this:

Threads:   7 total,   1 running,   6 sleeping
PID %CPU COMMAND
9933 99.9 node <-- main thread
9934 0.0 node <-- libuv/V8
9935 0.0 node
9936 0.0 node
9937 0.0 node
9938 0.0 node
9939 0.0 node

These include:

  • 1 main thread executing your JavaScript
  • 4 libuv threads handling background I/O
  • 2 V8 threads managing memory and JIT compilation

Note: The I/O operations happen in parallel using these threads, but their callbacks still execute on the main thread — once the call stack is clear.

Why This Matters: CPU-Bound vs I/O-Bound

Node’s hidden threads handle I/O efficiently — like reading a file or making a web request. But for CPU-heavy tasks such as image processing or cryptographic operations, the main thread gets blocked, making your app sluggish.

To handle these situations:

  • Use worker threads for parallel processing
  • Offload to child processes if you need memory isolation or independence

These strategies allow you to keep the main thread responsive, especially in real-time applications like games or chat systems.

Cleaning Up

Press q to exit top, and then stop your background process:

kill -9 9933

No more rogue loops.

Conclusion

Here’s a quick recap of what we’ve learned:

  • A process is an independent unit with its own memory and instruction pointer.
  • A thread is a lightweight unit of execution inside a process that shares memory.
  • Node.js uses multiple hidden threads for background operations, despite JavaScript being single-threaded.
  • Node offloads I/O tasks to a libuv thread pool and uses V8 threads for garbage collection and optimization.
  • CPU-bound tasks can block the event loop and should be offloaded to threads or processes.

Understanding how Node.js manages concurrency under the hood can help you build faster, more resilient applications. Whether you’re scaling APIs or crunching data, making informed decisions about processes and threads can make all the difference.

--

--

Aditya Yadav
Aditya Yadav

Written by Aditya Yadav

Software Engineer who talks about tech concepts in web development https://www.linkedin.com/in/aditya-yadav-01/