Skip to content

Node.js Worker Threads Deep Dive — Complete Guide to Parallel Processing

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Node.js Worker Threads Deep Dive. We cover key concepts, practical examples, and best practices to help you master this topic.

Node.js worker threads enable parallel JavaScript execution within the same Process, sharing memory through SharedArrayBuffer while running CPU-intensive tasks without blocking the event loop.

What You'll Learn

By the end of this tutorial, you'll create worker threads, communicate via messages, share memory with SharedArrayBuffer, implement thread pools, and handle errors in parallel computation.

Why Worker Threads Matter

Unlike child processes, worker threads share the same process and can access shared memory. They are ideal for CPU-bound tasks like image processing, data transformation, and cryptography.

Real-World Use

A data analysis application processes 1 million records in parallel across 8 worker threads, using SharedArrayBuffer for results aggregation and Atomics for synchronization without copying data.

Worker Threads Path

flowchart LR
  A[Cluster Module] --> B[Worker Threads Deep]
  B --> C[Data Sharing]
  C --> D[N-API Addons]
  D --> E[Performance]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

Creating Workers

Create a Worker from a file path or inline code.

// main.js
const { Worker } = require("node:worker_threads");
const worker = new Worker("./processor.js");
worker.postMessage({ data: [1, 2, 3, 4, 5], operation: "double" });
worker.on("message", (result) => {
  console.log("Result:", result);
});
worker.on("error", (err) => {
  console.error("Worker error:", err);
});
worker.on("exit", (code) => {
  if (code !== 0) console.error("Worker exited with code:", code);
});
// processor.js
const { parentPort } = require("node:worker_threads");
parentPort.on("message", (msg) => {
  const result = msg.data.map((x) => x * 2);
  parentPort.postMessage(result);
});

Worker Thread Pool

Create a pool of reusable workers for multiple tasks.

const { Worker } = require("node:worker_threads");
const os = require("node:os");
class WorkerPool {
  constructor(workerPath, size = os.cpus().length) {
    this.workers = [];
    this.queue = [];
    for (let i = 0; i < size; i++) {
      const worker = new Worker(workerPath);
      worker.on("message", (result) => {
        worker.busy = false;
        worker.resolve(result);
        this.processQueue();
      });
      worker.busy = false;
      this.workers.push(worker);
    }
  }
  exec(data) {
    return new Promise((resolve) => {
      this.queue.push({ data, resolve });
      this.processQueue();
    });
  }
  processQueue() {
    if (!this.queue.length) return;
    const available = this.workers.find((w) => !w.busy);
    if (!available) return;
    const task = this.queue.shift();
    available.busy = true;
    available.resolve = task.resolve;
    available.postMessage(task.data);
  }
  terminate() {
    this.workers.forEach((w) => w.terminate());
  }
}

SharedArrayBuffer

Share memory between threads without copying data using SharedArrayBuffer.

// main.js
const { Worker } = require("node:worker_threads");
const buffer = new SharedArrayBuffer(1024);
const view = new Int32Array(buffer);
view[0] = 0;
const worker = new Worker("./shared-worker.js");
worker.postMessage(buffer);
worker.on("message", () => {
  console.log("Results:", Array.from(view).slice(0, 10));
});
// shared-worker.js
const { parentPort } = require("node:worker_threads");
parentPort.on("message", (buffer) => {
  const view = new Int32Array(buffer);
  for (let i = 0; i < view.length; i++) {
    view[i] = i * i;
  }
  parentPort.postMessage("done");
});

Error Handling in Workers

Workers can throw errors without crashing the main thread.

const { Worker } = require("node:worker_threads");
function runInWorker(fn) {
  return new Promise((resolve, reject) => {
    const worker = new Worker(`const { parentPort } = require("worker_threads");\n(${fn.toString()})()`, { eval: true });
    worker.on("message", resolve);
    worker.on("error", reject);
    worker.on("exit", (code) => {
      if (code !== 0) reject(new Error(`Worker exited with code ${code}`));
    });
  });
}
const result = await runInWorker(() => {
  parentPort.postMessage(42 * 2);
});
console.log("Worker result:", result);

Transferable Objects

Transfer ArrayBuffer ownership to workers without copying.

const { Worker, transferable } = require("node:worker_threads");
const buffer = new ArrayBuffer(1024 * 1024);
const worker = new Worker("./transfer-worker.js");
worker.postMessage(buffer, [buffer]);
console.log("Buffer transferred, detached in main:", buffer.byteLength === 0);

Common Mistakes

1. Creating Workers for I/O Operations

Worker threads are for CPU work, not I/O. Use async I/O for network and filesystem operations.

2. Not Terminating Workers

Workers keep the process alive. Terminate workers when they are no longer needed.

3. Over-Threading Beyond CPU Cores

More workers than CPU cores causes context switching overhead. One worker per CPU core is optimal.

4. Ignoring Worker Error Events

Unhandled worker errors crash the worker silently. Always attach error listeners.

5. Using Regular Array Instead of SharedArrayBuffer

Regular arrays are copied, not shared. Use SharedArrayBuffer for true memory sharing.

Practice Questions

1. What is the difference between worker threads and child processes?

Workers share the same process and memory. Child processes are separate processes with separate memory.

2. How do workers communicate with the main thread?

Via message passing with postMessage and on("message"). Also via SharedArrayBuffer for shared memory.

3. What is a transferable object?

An ArrayBuffer whose ownership is transferred to the worker, avoiding a copy. The sender loses access.

4. When should you use worker threads vs cluster?

Workers for CPU-bound tasks within one process. Cluster for scaling HTTP across multiple processes.

5. Challenge: Create a worker pool that processes image data in parallel.

class ImageProcessor {
  constructor() {
    this.pool = new WorkerPool("./image-worker.js");
  }
  async processImages(files) {
    const results = await Promise.all(files.map((f) => this.pool.exec(f)));
    return results;
  }
}

FAQ

Can worker threads access the filesystem?

Yes. Worker threads have access to all Node.js APIs including fs, path, and crypto.

Do worker threads share event loop?

No. Each worker has its own event loop and V8 instance.

How much memory does each worker use?

Each worker has its own V8 heap (approximately 4-8MB baseline). Shared memory via SharedArrayBuffer is not duplicated.

What is Atomics used for in workers?

Atomics provides synchronization primitives for SharedArrayBuffer access across threads.

Can I use worker threads with TypeScript?

Yes. Compile to JavaScript first, or use ts-node with worker_threads.

Mini Project: Parallel Data Processor

Build a system that processes large arrays across multiple workers.

const { Worker } = require("node:worker_threads");
class ParallelProcessor {
  constructor(workerScript, concurrency = require("os").cpus().length) {
    this.workerScript = workerScript;
    this.concurrency = concurrency;
  }
  async process(dataArray) {
    const chunkSize = Math.ceil(dataArray.length / this.concurrency);
    const chunks = [];
    for (let i = 0; i < dataArray.length; i += chunkSize) {
      chunks.push(dataArray.slice(i, i + chunkSize));
    }
    const workers = chunks.map((chunk) => {
      return new Promise((resolve, reject) => {
        const worker = new Worker(this.workerScript, { eval: true });
        worker.postMessage(chunk);
        worker.on("message", resolve);
        worker.on("error", reject);
      });
    });
    const results = await Promise.all(workers);
    return results.flat();
  }
}

What's Next

Node.js Worker Data Sharing Node.js Cluster Module Node.js N-API Addons

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro