Sitemap

A Deep Dive into Hidden Threads in Node.js

3 min readApr 19, 2025

--

Press enter or click to view image in full size

When people say “JavaScript is single-threaded,” they’re mostly right — but if you’re working with Node.js, the full story is far more interesting.

Under the hood, Node.js is a multithreaded runtime. Even though your JavaScript code runs on a single thread, Node itself uses multiple threads behind the scenes to keep things fast, efficient, and non-blocking. This article explores how those threads work, what role they play, and how they interact with your code.

Why Does Node.js Need Hidden Threads?

JavaScript was originally designed to run in browsers — environments that operate on a single thread where one task runs at a time. But server-side applications often involve I/O operations like reading files, querying databases, or making HTTP requests. If these were executed synchronously, they would block the main thread and significantly degrade performance.

To solve this, Node.js leverages libuv, a C-based library that provides an event loop and thread pool, enabling non-blocking I/O operations.

The Thread Breakdown

A typical Node.js process includes seven threads by default:

Thread Type Count Purpose Main thread 1 Executes your JavaScript code libuv worker threads 4 Handles asynchronous I/O (file system, DNS, etc.) V8 internal threads 2 Manages garbage collection and JIT compilation

Let’s break down the responsibilities of each.

1. The Main Thread

This is where your JavaScript code executes — including variables, functions, and event listeners.

Limitations:

  • Only one operation executes at a time.
  • Blocking operations (like CPU-intensive computations) freeze the event loop, stalling the entire application.

2. The libuv Thread Pool

libuv creates a thread pool with four worker threads by default to handle expensive operations outside the main thread.

Tasks delegated to libuv include:

  • File system access (e.g., fs.readFile)
  • DNS lookups (non-cached)
  • Compression, encryption, and hashing operations

Execution flow for a file read:

  1. The main thread calls fs.readFile().
  2. libuv assigns the task to a worker thread.
  3. The worker completes the operation.
  4. The callback is pushed onto the event loop.
  5. Once the main thread is idle, the callback is executed.

While the callback runs on the main thread, the I/O operation itself happens in a worker thread, ensuring non-blocking behavior.

3. V8 Internal Threads

Node.js uses the V8 JavaScript engine, which maintains two internal threads:

  • Garbage Collector: Frees up unused memory.
  • JIT Compiler: Converts frequently used code into machine-level instructions for better performance.

These internal threads optimize execution without affecting your main thread.

Observing the Hidden Threads

To see these threads in action:

  1. Start a background Node process:
node process.js &

2. Check its threads using:

top -H -p <PID>

Output:

Threads:   7 total,   1 running,   6 sleeping 
PID %CPU COMMAND
9933 99.9 node <-- main thread
9934 0.0 node <-- background thread
...

This confirms that a Node.js process spawns multiple threads under the hood.

CPU-Bound Work: The Catch

While hidden threads handle I/O very efficiently, they do not help with CPU-intensive operations such as:

  • JSON parsing of large datasets
  • Cryptographic calculations
  • Image or video processing

These operations execute on the main thread and block the event loop, impacting performance.

Solutions:

  • Use the worker_threads module for CPU-heavy tasks.
  • Use the child_process module for process-level isolation.
  • Offload tasks to external services or message queues.

Best Practices and Tuning Tips

  • Avoid CPU-intensive logic on the main thread.
  • Use worker_threads when concurrency is needed.
  • For memory isolation or high parallelism, prefer spawning new processes.
  • You can increase the libuv thread pool (up to 128) if your application is I/O intensive:
UV_THREADPOOL_SIZE=8 node app.js

Conclusion

Although JavaScript runs in a single thread, Node.js is designed with concurrency in mind. Thanks to libuv and V8, Node offloads time-consuming operations to background threads and keeps your applications responsive and efficient.

--

--

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/