Home Engineering

Is Node.js Really Single-Threaded?

24 June 2026 · 3 min read
Table of contents

“Node.js is single-threaded.”

If that’s true, how can one server handle thousands of users at the same time?

The Short Answer

JavaScript runs on one main thread. But Node.js uses multiple threads behind the scenes for heavy work like reading files or making network calls.

What Actually Happens

Drag · Scroll to zoom

Your request enters the Event Loop, heavy work gets handed to the Thread Pool, and the result comes back when ready.

  • User sends a request (read a file, call a database)
  • Node.js receives it and passes it to the Event Loop
  • Event Loop spots the heavy work and offloads it to the Thread Pool
  • Thread Pool handles it in the background using multiple OS threads
  • When done, the result returns to the Event Loop and your callback runs

Explain Like I’m 12

Think of Node.js like a restaurant:

  • The waiter (Event Loop) takes orders from every table
  • The kitchen (Thread Pool) does the actual cooking
  • The customer (your request) doesn’t block other tables while food is being prepared
  • When the food (result) is ready, the waiter delivers it

The waiter never stands around at one table waiting. That’s how one server handles thousands of requests at once.

Example

const fs = require("fs");

fs.readFile("big-file.txt", () => {
  console.log("done");
});

console.log("continue");

Output:

continue
done

Why? fs.readFile is handed off to the Thread Pool immediately. Node.js doesn’t wait for it. The next line runs right away. When the file is ready, the callback fires and prints “done”.

Why People Call It Single-Threaded

JavaScriptNode.js Runtime
ThreadsSingleMultiple
RoleRuns your logicHandles background work

Your JavaScript always runs on one thread. The runtime uses more threads under the hood so your code never blocks.

Inside the Thread Pool

The Thread Pool is provided by libuv, a C library bundled inside Node.js. libuv actually gives Node.js two different async tools:

Thread PoolOS Async I/O
Used forFile I/O, DNS, cryptoNetwork (TCP/UDP)
Threads4 by defaultNone needed

Network requests skip the Thread Pool entirely. The OS handles them through its own async system, so they don’t compete for those 4 slots.

Default size: 4 threads. You can raise it with an environment variable:

UV_THREADPOOL_SIZE=8 node server.js

Uses the Thread Pool:

  • fs.readFile, fs.writeFile, file stats
  • dns.lookup()
  • Crypto: pbkdf2, randomBytes, bcrypt (via native addons)

Skips the Thread Pool:

  • http.get(), https.get()
  • TCP and UDP sockets
  • dns.resolve()

Why this matters: if 100 requests all call fs.readFile at once, only 4 run at the same time. The rest queue up and wait. Network requests don’t have this bottleneck.

Benefits

  • Simple mental model: no locks, no race conditions in your JS code
  • Handles many concurrent users efficiently
  • Great for APIs and real-time applications
  • Great for network-heavy workloads

The Limitation

If one JavaScript task takes too long, everyone waits. The main thread is stuck, and background threads can’t help.

This is a problem for:

  • Video encoding
  • Large calculations
  • Image processing

For CPU-heavy work, Node.js is the wrong tool.

Final Takeaway

Node.js is not “only one thread”. A better description is: single-threaded JavaScript running on top of a multi-threaded runtime.