Glossary / Node.js worker threads
Node.js worker threads
node:worker_threads lets Node.js run JavaScript in additional threads within one process. Workers are useful for CPU-intensive JavaScript that would otherwise block the main event loop; they usually do not improve I/O-heavy workloads, because Node's asynchronous I/O is already designed to scale without one thread per operation.
Creating and communicating with workers
A worker has its own JavaScript execution context and event loop. The parent and worker communicate through messages using structured clone; ArrayBuffer can be transferred, while SharedArrayBuffer can be shared with synchronization responsibilities.
// main.mjs
import { Worker } from 'node:worker_threads';
const worker = new Worker(new URL('./compute.mjs', import.meta.url));
worker.once('message', result => console.log(result));
worker.once('error', error => console.error(error));
worker.once('exit', code => {
if (code !== 0) console.error(`worker stopped: ${code}`);
});
worker.postMessage({ input: [1, 2, 3] });
A worker can use parentPort to receive and reply. Spawning a worker has startup and memory costs, so production CPU pools typically reuse a bounded set of workers and queue jobs. Define job IDs, validation, timeouts, cancellation, crash handling, and pool shutdown. A worker's error event and exit code are distinct lifecycle signals; clients should settle each job once.
Choosing a process or thread
Worker threads share a process and can share memory, making them lower-overhead than child processes for some CPU tasks. Child processes provide stronger isolation and separate address spaces, and are often preferable for running external commands or containing crashes/security boundaries. Threads do not make CPU work automatically faster: serialization, contention, oversubscription, and memory-bandwidth costs can dominate.
Engineering guidance
Benchmark against the event-loop delay target and include serialization and startup costs. Bound pool size and queue length. Never assume messages are trusted or valid. Avoid shared mutable state unless the algorithm needs it and has a clear atomic protocol. Observe worker utilization, queue wait, job duration, and crash rates.
Interview questions
- When are worker threads appropriate? CPU-bound JavaScript that should not monopolize the main event loop.
- Why use a pool? Worker startup and per-thread resources are costly; reuse bounds concurrency and resource use.
- How does transfer differ from shared memory? Transfer moves ownership of transferable data; shared memory allows concurrent access and requires synchronization.
- When choose a child process? For stronger isolation, separate address space, external commands, or crash containment.
- What should a job protocol handle? IDs, results/errors, cancellation/timeouts, worker failure, queue bounds, and shutdown.