When would you use worker_threads, cluster or child_process in Node.js?
Use worker_threads for CPU-heavy JavaScript inside one process, cluster to run several copies of a server across CPU cores sharing one port, and child_process to run a separate program or script such as ffmpeg or a Python file.
Explain it simply
Imagine a small catering business during wedding season.
- worker_threads is like hiring extra cooks in the same kitchen. They share the building and can pass dishes to each other quickly, but each cook has their own workstation.
- cluster is like opening several identical counters of the same stall, with a manager at the entrance sending each customer to a free counter.
- child_process is like calling an outside vendor, say a sweet shop, to do a job you do not do yourself. They work separately and send back the result.
All three exist because one Node process runs your JavaScript on one main thread. They are different ways to use more of the machine.
Example
Moving a heavy calculation off the main thread with worker_threads:
// main.js
const { Worker } = require("node:worker_threads");
function runHeavyTask(n) {
return new Promise((resolve, reject) => {
const worker = new Worker("./prime-worker.js", { workerData: n });
worker.once("message", resolve);
worker.once("error", reject);
});
}
runHeavyTask(5_000_000).then((count) => console.log("Primes:", count));// prime-worker.js
const { parentPort, workerData } = require("node:worker_threads");
let count = 0;
for (let i = 2; i <= workerData; i++) {
let isPrime = true;
for (let j = 2; j * j <= i; j++) {
if (i % j === 0) { isPrime = false; break; }
}
if (isPrime) count++;
}
parentPort.postMessage(count);Running an external program with child_process:
const { execFile } = require("node:child_process");
execFile("ffmpeg", ["-i", "input.mp4", "-vf", "scale=640:-1", "small.mp4"], (err) => {
if (err) return console.error("Conversion failed", err.message);
console.log("Video resized");
});How it works
| worker_threads | cluster | child_process | |
|---|---|---|---|
| What it creates | Threads in the same process | Multiple Node processes | Any separate process |
| Memory | Separate JS heaps, can share SharedArrayBuffer | Fully separate | Fully separate |
| Communication | postMessage, fast | IPC messages | stdin/stdout or IPC |
| Best for | CPU work in JS: hashing, image or PDF processing, parsing | Using all cores for an HTTP server | Running other programs or scripts |
cluster forks workers with cluster.fork(). The primary process accepts connections and distributes them to workers (round-robin by default on most platforms except Windows), so all workers can listen on the same port.
Real-world use
- A resume-parsing or PDF-generation service runs the parsing in a worker pool so the API stays responsive.
- An older Express app on an 8-core server uses cluster or PM2 cluster mode to run 8 copies.
- A media service calls
ffmpegor a Python ML script throughchild_process.spawn.
In containers and Kubernetes, people often skip cluster and run one process per container, letting the platform scale replicas instead.
Common mistakes
- Using worker threads for I/O. Node already handles I/O without blocking; workers only help with CPU work.
- Creating a new worker for every request. Start-up has a cost; use a pool.
- Storing sessions in memory with cluster. Each worker has its own memory, so use Redis or a database for shared state.
- Passing user input to
exec, which runs through a shell and allows command injection. PreferexecFileorspawnwith an argument array.
What to say in the interview
"Node runs JavaScript on one thread, so these are three ways to use more CPU. worker_threads creates threads inside the same process; I use them for CPU-heavy JavaScript like hashing or image processing, usually with a worker pool, and they communicate with postMessage or shared memory. cluster forks multiple copies of the server process that share one port, so an HTTP app can use every core, though in Kubernetes I would usually scale with replicas instead. child_process runs separate programs such as ffmpeg or a Python script. For plain I/O I need none of these, because the event loop already handles it."
Follow-up questions
- Why do worker threads not help with database-heavy APIs?
- How would you share session data between cluster workers?
- What is the difference between
spawn,exec,execFileandfork?
Go deeper
Read these once the basics above feel easy.
Data sent with postMessage is copied using the structured clone algorithm. For large binary data you can transfer an ArrayBuffer (ownership moves, no copy) or use a SharedArrayBuffer with Atomics for true shared memory. child_process.fork() is a special case of spawn that starts a new Node process with an IPC channel; cluster is built on top of it. Each worker thread has its own event loop and V8 isolate, so it costs several megabytes of memory, which is another reason to pool them.