Concept
The beginner framing: a single Node.js process, no matter how well-optimized, only ever uses one CPU core, on an 8-core machine, that's 7 cores sitting idle unless something explicitly puts them to work.
The precise mental model: Node offers three distinct mechanisms for using more than one core, and they solve genuinely different problems, not interchangeable versions of the same thing:
| Mechanism | What it creates | Best for | Memory |
|---|---|---|---|
cluster | Multiple full Node processes, load-balanced | Scaling network I/O (many concurrent requests) across cores | Separate per worker, no shared state |
worker_threads | Real OS threads within the same process | CPU-bound computation (heavy synchronous work) | Can share memory via SharedArrayBuffer |
child_process | A separate process running any program | Running another program, or full isolation for a separate task | Fully separate, communicate via IPC/stdio |
cluster: scaling network I/O across cores
const cluster = require("cluster");
const os = require("os");
if (cluster.isPrimary) {
const numCPUs = os.cpus().length;
for (let i = 0; i < numCPUs; i++) cluster.fork(); // one worker PER CORE
cluster.on("exit", (worker) => {
console.log(`worker ${worker.process.pid} died, forking a replacement`);
cluster.fork();
┌─────────────┐
incoming ───────▶ │ PRIMARY │ (load-balances connections,
requests │ (1 process)│ round-robin across workers)
└──────┬──────┘
┌─────────────┼─────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ WORKER 1 │ │ WORKER 2 │ │ WORKER 3 │ ← separate processes,
│ own loop │ │ own loop │ │ own loop │ separate memory,
│ own mem │ │ own mem │ │ own mem │ one per CPU core
└──────────┘ └──────────┘ └──────────┘The primary process forks one worker per CPU core; each worker is a complete, independent Node process with its own event loop and its own memory, the primary distributes incoming connections across them (round-robin on most platforms). This lets a single machine handle roughly numCPUs times the concurrent request throughput of a single process, since each worker's main thread handles its own share independently.
worker_threads: offloading CPU-bound work
const { Worker } = require("worker_threads");
function runHeavyComputation(data) {
return new Promise((resolve, reject) => {
const worker = new Worker("./heavy-computation.js", { workerData: data });
worker.on("message", resolve);
worker.on("error", reject);
});
}Unlike cluster's separate processes, worker_threads creates genuine OS threads within the same process, confirmed stable in this app's installed Node v23.11.0. This matters specifically for CPU-bound work: offloading a heavy synchronous computation (image processing, complex data transformation) to a worker thread keeps the main thread free to keep handling I/O and other requests, without the heavier overhead of spawning an entirely separate process the way cluster/child_process do.