Worker Threads & Cluster
Worker threads let Node run CPU-heavy JavaScript on separate threads without blocking the main event loop, while the cluster module forks multiple processes to scale a server across all your CPU cores.
Learn Worker Threads & Cluster in our free Node.js course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…
Part of the free Node.js course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
By the end of this lesson you'll know when CPU-bound work needs a worker thread, how to pass data with workerData and message with parentPort, and how cluster differs from both worker_threads and child_process for scaling real applications.
What You'll Learn in This Lesson
1️⃣ Your First Worker Thread
A worker is a separate JavaScript file that runs on its own thread. You create one with new Worker(path, options) , hand it input through the workerData option, and get a result back when it calls parentPort.postMessage(...) . Wrapping the worker in a Promise makes it easy to await .
The worker below does a CPU-bound loop (summing a million numbers). Crucially, the main thread stays free the whole time — that's the entire point of using a thread. You need two files placed next to each other.
2️⃣ Two-Way Messaging
Beyond a one-shot result, a worker can act like a little service you talk to repeatedly. Both sides have a port: the worker uses parentPort.on('message', ...) and parentPort.postMessage(...) ; the main thread uses worker.on('message', ...) and worker.postMessage(...) .
When you're finished, call worker.terminate() so the thread shuts down and your program can exit cleanly.
3️⃣ cluster — Scaling a Server
Worker threads keep one heavy task from freezing the loop. cluster solves a different problem: a single Node process uses one core, so on an 8-core machine you'd waste 7. cluster forks one Node process per core, and they all share the same listening port — the operating system spreads incoming connections across them.
The example below shows the shape: the primary forks a worker per core and restarts any that die, while each worker runs an identical HTTP server. The PIDs will differ every run, so treat the output as representative.
Your turn. The program below works once you fill in the two blanks marked ___ . Follow the 👉 hints, write the tiny worker file shown in the comment, then run it.
No blanks this time — just a brief and an outline. Build the two files, run them, and check your output against the example in the comments. This ties together workerData , parentPort , and a Promise wrapper.
📋 Quick Reference — Pick the Right Tool
Practice quiz
When should you reach for worker_threads?
- For database and network I/O
- For CPU-bound work that would block the main thread
- To make HTTP requests faster
- For logging
Answer: For CPU-bound work that would block the main thread. Workers are for heavy computation; async/await already handles I/O efficiently.
Which import gives you the Worker class?
- node:worker_threads
- node:cluster
- node:child_process
- node:threads
Answer: node:worker_threads. import { Worker } from 'node:worker_threads' creates worker threads.
How do you pass initial input into a worker at creation?
- As a global variable
- Via process.argv
- Through the workerData option
- By writing a file
Answer: Through the workerData option. The workerData option is copied into the worker, readable via imported workerData.
How does a worker send a result back to the main thread?
- return value
- console.log
- global.result =
- parentPort.postMessage(...)
Answer: parentPort.postMessage(...). parentPort.postMessage(data) sends a message the main thread receives on 'message'.
How are messages between main and worker transferred?
- By structured clone (copied)
- By shared reference
- As JSON strings only
- They aren't — workers can't message
Answer: By structured clone (copied). Messages are structured-cloned, so plain objects/buffers travel but functions don't.
What is the difference between worker_threads and child_process?
- They are identical
- worker_threads share a process; child_process spawns separate OS processes
- child_process is faster for CPU work
- worker_threads can't run JavaScript
Answer: worker_threads share a process; child_process spawns separate OS processes. Threads are in-process and lighter; child processes are isolated, separate processes.
What does the cluster module do?
- Encrypts traffic
- Validates requests
- Forks one process per core to scale a server across cores
- Replaces async/await
Answer: Forks one process per core to scale a server across cores. cluster forks a worker process per CPU core, all sharing the listening port.
How do you shut a worker down when you are done with it?
- worker.stop()
- worker.kill()
- delete worker
- worker.terminate()
Answer: worker.terminate(). worker.terminate() ends the thread so your program can exit cleanly.
What reliably resolves the worker file path in an ES module?
- new URL('./worker.mjs', import.meta.url)
- './worker.mjs'
- __dirname + '/worker.mjs'
- process.cwd()
Answer: new URL('./worker.mjs', import.meta.url). A bare relative path can resolve wrongly; new URL with import.meta.url is safe.
What does cluster NOT solve?
- Scaling request throughput across cores
- Restarting workers that die
- Unblocking one heavy calculation on the main thread
- Sharing a listening port
Answer: Unblocking one heavy calculation on the main thread. cluster scales throughput; use worker_threads to offload one heavy calculation.