Node.js Worker Threads — Complete Guide to Multithreading and Parallel Processing
In this tutorial, you will learn about Node.js Worker Threads. We cover key concepts, practical examples, and best practices to help you master this topic.
Node.js worker threads enable running JavaScript code in parallel threads within the same Process, ideal for CPU-intensive tasks without blocking the event loop.
What You'll Learn
By the end of this tutorial, you'll create worker threads, communicate between main and worker threads, share memory, handle thread errors, and parallelize CPU-heavy computations.
Why Worker Threads Matter
Node.js is single-threaded for JavaScript execution. CPU-intensive operations (image processing, data Parsing, cryptography) block the event loop. Worker threads offload these tasks to separate threads.
Real-World Use
A PDF generation service uses worker threads to render documents in parallel. While one worker generates a 100-page PDF, the main thread continues serving HTTP requests without delay.
Worker Threads Learning Path
flowchart LR
A[Cluster] --> B[Worker Threads]
B --> C[Error Handling]
C --> D[Debugging]
D --> E[Express.js]
A --> F{You Are Here}
style F fill:#f90,color:#fff
Creating a Worker Thread
// main.js
import { Worker } from "node:worker_threads";
const worker = new Worker("./worker.js", { workerData: { count: 10 } });
worker.on("message", (result) => console.log("Result:", result));
worker.on("error", (err) => console.error("Worker error:", err.message));
worker.on("exit", (code) => console.log(`Worker exited with code ${code}`));
// worker.js
import { parentPort, workerData } from "node:worker_threads";
let sum = 0;
for (let i = 0; i < workerData.count * 1000000; i++) {
sum += i;
}
parentPort.postMessage(sum);
Communication Between Threads
// main.js
import { Worker } from "node:worker_threads";
const worker = new Worker("./message-worker.js");
worker.postMessage({ cmd: "process", data: [1, 2, 3, 4, 5] });
worker.on("message", (msg) => console.log("Worker says:", msg));
// message-worker.js
import { parentPort } from "node:worker_threads";
parentPort.on("message", (msg) => {
if (msg.cmd === "process") {
const result = msg.data.map(x => x * x);
parentPort.postMessage({ result });
}
});
SharedArrayBuffer
For high-performance data sharing without message copying.
// main.js
import { Worker } from "node:worker_threads";
const buffer = new SharedArrayBuffer(4 * 1024);
const view = new Int32Array(buffer);
view[0] = 42;
const worker = new Worker("./shared-worker.js", { workerData: buffer });
setTimeout(() => console.log("Value from worker:", view[0]), 1000);
// shared-worker.js
import { workerData, parentPort } from "node:worker_threads";
const view = new Int32Array(workerData);
view[0] = view[0] * 2;
parentPort.postMessage("done");
Worker Pool Pattern
Managing multiple workers efficiently.
import { Worker } from "node:worker_threads";
import { cpus } from "node:os";
class WorkerPool {
constructor(workerFile, poolSize = cpus().length) {
this.workers = [];
this.queue = [];
for (let i = 0; i < poolSize; i++) {
const worker = new Worker(workerFile);
worker.on("message", (result) => {
worker.busy = false;
worker.resolve(result);
this.processQueue();
});
worker.busy = false;
this.workers.push(worker);
}
}
runTask(data) {
return new Promise((resolve) => {
this.queue.push({ data, resolve });
this.processQueue();
});
}
processQueue() {
const available = this.workers.find(w => !w.busy);
if (available && this.queue.length) {
available.busy = true;
const task = this.queue.shift();
available.resolve = task.resolve;
available.postMessage(task.data);
}
}
}
Error Handling in Workers
// main.js
const worker = new Worker("./failing-worker.js");
worker.on("error", (err) => {
console.error("Worker crashed:", err.message);
});
worker.on("exit", (code) => {
if (code !== 0) console.error(`Worker failed with code ${code}`);
});
// failing-worker.js
import { parentPort } from "node:worker_threads";
try {
throw new Error("Something broke");
} catch (err) {
parentPort.postMessage({ error: err.message });
}
Common Mistakes
1. Creating Too Many Workers
Each worker thread has overhead. Creating more workers than CPU cores causes context switching overhead.
2. Blocking Workers with Heavy Operations
Workers run JavaScript, not native threads for I/O. Heavy synchronous operations still block the worker's event loop.
3. Not Handling Worker Errors
Unhandled errors in workers crash the thread. Always listen for 'error' events and handle try/catch in workers.
4. Transferring Large Data by Copy
postMessage copies data by default. Use transferList or SharedArrayBuffer for large data to avoid memory overhead.
5. Using Workers for I/O Operations
I/O operations (file reads, HTTP requests) are already non-blocking. Workers are for CPU-bound tasks only.
Practice Questions
1. What is the difference between worker_threads and cluster?
Worker threads share the same process (shared memory, less isolation). Cluster forks separate processes (isolated memory, more overhead).
2. When should you use a worker thread?
For CPU-intensive tasks like image processing, data compression, JSON parsing, or complex calculations that would block the event loop.
3. How do you share memory between threads?
Use SharedArrayBuffer. It lets multiple threads read and write the same memory without Serialization overhead.
4. What happens if a worker thread throws an unhandled error?
The worker emits an 'error' event and terminates. The main thread can catch it and spawn a replacement.
5. Challenge: Create a worker pool that processes an array of numbers in parallel, computing Fibonacci for each.
// fib-worker.js
import { parentPort } from "node:worker_threads";
function fib(n) { return n <= 1 ? n : fib(n - 1) + fib(n - 2); }
parentPort.on("message", (n) => parentPort.postMessage(fib(n)));
// main.js — pool of 4 workers computing fib(40) for 8 inputs
// (pool implementation from above; runTask sends each number)
FAQ
Mini Project: Image Resizer
Create a worker thread that resizes images using sharp, keeping the main thread responsive.
// resize-worker.js
import { parentPort, workerData } from "node:worker_threads";
import sharp from "sharp";
const { inputPath, outputPath, width, height } = workerData;
sharp(inputPath).resize(width, height).toFile(outputPath, (err) => {
parentPort.postMessage(err ? { error: err.message } : { success: true });
});
What's Next
Node.js Error Handling Node.js Testing Express.js
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro