CPCODELAB
Node.js

Worker Threads: True Parallelism in Node.js

12 min read

Worker Threads

Node.js is single-threaded by default, but the worker_threads module lets you spawn OS threads that share memory via SharedArrayBuffer. Unlike child_process, worker threads live inside the same process and have low spawn overhead, making them ideal for CPU-intensive tasks.

Spawning a Worker

js
// main.js
const { Worker } = require("worker_threads");

function runWorker(data) {
  return new Promise((resolve, reject) => {
    const worker = new Worker("./worker.js", { workerData: data });
    worker.on("message", resolve);
    worker.on("error", reject);
    worker.on("exit", (code) => {
      if (code !== 0) reject(new Error(`Worker exited with code ${code}`));
    });
  });
}

async function main() {
  const result = await runWorker({ n: 40 });
  console.log("Fibonacci(40) =", result); // 102334155
}

main();
js
// worker.js
const { workerData, parentPort } = require("worker_threads");

function fib(n) {
  if (n <= 1) return n;
  return fib(n - 1) + fib(n - 2);
}

parentPort.postMessage(fib(workerData.n));

Shared Memory with SharedArrayBuffer

js
// Share a typed array between main thread and worker
const { Worker, isMainThread, workerData } = require("worker_threads");

if (isMainThread) {
  const sharedBuffer = new SharedArrayBuffer(4); // 4 bytes = 1 Int32
  const arr = new Int32Array(sharedBuffer);
  arr[0] = 0;

  const w = new Worker(__filename, { workerData: { sharedBuffer } });
  w.on("exit", () => console.log("Counter:", arr[0])); // 1000
} else {
  const arr = new Int32Array(workerData.sharedBuffer);
  for (let i = 0; i < 1000; i++) Atomics.add(arr, 0, 1);
}

Use Atomics operations whenever multiple threads write to the same SharedArrayBuffer position — without them, concurrent writes produce race conditions and unpredictable values.

Ready to test yourself?
Practice Node.js— quiz & coding exercises