Worker Threads
Overview
If your Node.js API endpoint needs to resize a massive 4K image, calculate a cryptographic hash, or process a million-row CSV, that heavy math is done entirely by V8 on the Single Main Thread. This causes 'Event Loop Blocking'; the server completely freezes, and no other users can connect. Historically, you had to spawn heavy Child Processes to fix this. In modern Node, you use Worker Threads (worker_threads). They allow you to spin up multiple independent V8 engines inside the same process, effectively giving Node true, native multi-threading capabilities for CPU-intensive tasks.
Syntax
// --- 1. MAIN THREAD (server.js) ---
const { Worker } = require('worker_threads');
app.get('/heavy-math', (req, res) => {
// 1. Spin up a new thread and give it the heavy math file
const worker = new Worker('./heavyTask.js', { workerData: { max: 1000000000 } });
// 2. Listen for the result from the thread!
worker.on('message', (result) => {
res.send(`Math complete! Result: ${result}`);
});
worker.on('error', (err) => res.status(500).send(err.message));
});
// --- 2. WORKER THREAD (heavyTask.js) ---
const { parentPort, workerData } = require('worker_threads');
// This heavy math runs on a completely separate CPU core!
// The Main Thread is 100% free to serve other users while this runs.
let count = 0;
for (let i = 0; i < workerData.max; i++) {
count++;
}
// Send the result back to the Main Thread!
parentPort.postMessage(count);Common Pitfalls
- Using Worker Threads for I/O tasks. You should NEVER use a Worker Thread to read a file, query a database, or make an HTTP request. Node's default asynchronous I/O (via
libuv) is already flawlessly optimized for that. Worker Threads are strictly for heavy CPU/Math tasks. - Passing massive JSON objects back and forth. The Main Thread and the Worker Thread do NOT share memory by default. When you pass data via
postMessage(), Node physically clones the data using the structured clone algorithm. If you pass a 500MB JSON object, Node will freeze trying to copy it. You must useSharedArrayBufferif you truly need shared memory.
Interview Questions
A Child Process or Cluster spins up an entirely new OS process with its own PID, requiring 30MB+ of fresh RAM and slow IPC (Inter-Process Communication). A Worker Thread shares the exact same OS process and PID, uses significantly less RAM, and can physically share memory via SharedArrayBuffer.
Real-World Example
Using the popular piscina library to manage a pool of Worker Threads, ensuring we don't accidentally spin up 1,000 threads and crash the server.
// npm install piscina
const Piscina = require('piscina');
const path = require('path');
// Creates a persistent pool of threads (usually equal to CPU cores)
const pool = new Piscina({
filename: path.resolve(__dirname, 'imageProcessor.js')
});
app.post('/resize-image', async (req, res) => {
// Hands the task to the next available free thread in the pool!
const result = await pool.run({ imageBuffer: req.file.buffer });
res.send(result);
});Check Your Knowledge
Test your understanding of Worker Threads with these quick questions.