Node.js Cluster vs Worker Threads: Which to Use
Node's cluster module forks whole processes to use every CPU core; worker_threads share memory inside one process. How to pick between them.
Node’s cluster module and its worker_threads module both let a Node.js application use more than one CPU core, but they solve different problems: cluster forks separate OS processes to scale a network server across cores, while worker_threads runs actual threads inside a single process to offload CPU-heavy work without blocking it.
Why either exists at all
Node.js runs your JavaScript on a single thread, driven by the event loop. That’s fine for I/O-bound work — reading files, querying a database, handling HTTP requests — because Node hands those operations off to the OS or a thread pool and keeps the main thread free. It’s not fine for CPU-bound work: image resizing, PDF generation, cryptographic hashing, or any tight computational loop blocks that single thread and stalls every other request the process is handling. Both cluster and worker_threads exist to get around that single-thread ceiling, just for different kinds of problems.
How the cluster module works
cluster forks the current process N times, typically once per CPU core, and each fork (called a worker) runs the full application independently — its own event loop, its own memory, its own copy of every loaded module. A primary process usually just distributes incoming connections across the workers, either by letting the OS load-balance them or with round-robin scheduling built into cluster itself.
const cluster = require("node:cluster");
const os = require("node:os");
if (cluster.isPrimary) {
for (let i = 0; i < os.availableParallelism(); i++) cluster.fork();
} else {
require("./server"); // each worker runs the full app
}
Because each worker is a separate process, a crash in one doesn’t take down the others, and there’s no shared memory to worry about corrupting. This is the model most production Node deployments already use in some form — it’s effectively what a process manager or a container orchestrator’s replica count achieves anyway. If you’re deploying behind Kubernetes or another orchestrator that already runs multiple replicas, cluster is often redundant: you get the same core-utilization benefit by running one Node process per pod and scaling pod count, which also sidesteps cluster’s more awkward parts (sticky sessions for stateful WebSocket connections, for instance).
How worker_threads works
worker_threads spins up real OS threads inside the same process. Each thread gets its own V8 isolate and its own event loop, so a worker thread can run a CPU-heavy loop without blocking the main thread — but unlike cluster, threads can share memory directly through SharedArrayBuffer and pass data efficiently with MessagePort, without the serialization overhead of inter-process communication.
const { Worker } = require("node:worker_threads");
const worker = new Worker("./cpu-heavy-task.js", {
workerData: { input: largePayload },
});
worker.on("message", (result) => console.log(result));
This is the right tool for a specific, boundable chunk of CPU work triggered by a request — hashing a password, transcoding an uploaded file, running a data transformation — where you want the main thread free to keep serving other requests while that one task runs.
Cluster vs worker_threads
cluster | worker_threads | |
|---|---|---|
| Isolation unit | Full OS process | Thread within one process |
| Memory | Separate, no sharing | Shared via SharedArrayBuffer |
| Startup cost | Higher (new process) | Lower (new thread) |
| Crash blast radius | Isolated to one worker | Can be isolated per-thread, but shares the process |
| Best for | Scaling a whole server across cores | Offloading one CPU-bound task |
| Redundant when | Already running multiple replicas (e.g. Kubernetes) | Rarely — orthogonal to process count |
Picking between them
If the goal is “use all the CPU cores this machine has to serve more requests,” and nothing else in your infrastructure already does that, cluster is the straightforward answer — though check first whether your deployment platform (a container orchestrator, a PaaS with built-in horizontal scaling and a load balancer in front) is already achieving the same thing at a different layer, in which case adding cluster on top just adds complexity for no gain.
If the goal is “this one operation is CPU-heavy and blocks everything else while it runs,” worker_threads is the fix, regardless of whether you’re also using cluster — the two aren’t mutually exclusive, and a clustered worker can itself spawn threads for its own heavy tasks.
The takeaway
cluster scales a server horizontally by forking whole processes, one per core, with full isolation and no shared memory. worker_threads scales a single process by running actual threads that can share memory, and it’s the better fit for offloading one bounded CPU-heavy task without touching how the rest of the server is deployed. Reach for cluster only if nothing in your deployment stack is already multiplying process count for you; reach for worker_threads whenever a specific computation, not the whole server, is the bottleneck.
Tagged
Keep reading
Takina · · 4 min read Node.js Worker Threads Explained: True Parallelism
Node.js worker threads run JavaScript on real OS threads in parallel, letting CPU-heavy work run without blocking the event loop.
Takina · · 3 min read What Is a SharedWorker? Explained
A SharedWorker runs one script shared across every tab and window of the same origin — a single background thread they can all connect to and message.
Takina · · 4 min read Bun vs Deno: Comparing the Two Node.js Alternatives
Bun and Deno both aim to replace Node.js with a faster, more batteries-included runtime. How their runtimes, tooling, and compatibility differ.