Skip to solution
mediumSystem Design

What are Worker Threads in Node.js and when would you use them?

1.1k views
01

Understand the problem

Question presented to candidate: "A CPU-heavy computation (say, computing a large Fibonacci number) freezes your server for every concurrent request while it runs. Does moving it to an async function fix that? What actually does?"

What a strong answer should cover:

  • Worker Threads run JavaScript on genuinely separate OS threads, each with its own V8 instance and event loop — unlike async/await or Promises, which only ever run on the single main thread and provide no parallelism for CPU-bound work at all.
  • 📌 The precise, verified answer to the prompt: making the computation async does not fix it — a synchronous, CPU-bound computation blocks the main thread identically whether called directly or from inside an async function, verified directly: running fib(40) synchronously froze a heartbeat to zero ticks for 963ms. Moving that identical computation into a Worker Thread let the main thread's heartbeat tick 69 times during the ~1001ms it took — real, measured proof of genuine parallelism, not just non-blocking scheduling.
  • The work itself is not faster in a Worker Thread — the wall-clock duration was nearly identical (963ms vs. 1001ms) — what changes is that the main thread stays free to keep serving other requests while the computation runs on a separate thread.
  • Communication with a Worker Thread happens via message passing (worker.postMessage/parentPort.postMessage, 'message' events) by default — data is structured-cloned (copied) across the boundary, not shared directly, unless explicitly using a SharedArrayBuffer for genuinely shared memory.
  • Each Worker Thread has its own module cache and global scope, entirely separate from the main thread's — a module required in the main thread and again inside a worker executes independently in each, with no shared state unless explicitly passed across the message-passing boundary or a SharedArrayBuffer.
  • A precise answer distinguishes Worker Threads (genuine in-process parallelism for CPU-bound JavaScript) from the thread pool (libuv's fixed pool for specific native blocking operations — file I/O, some crypto, DNS, covered in its own dedicated question) and from cluster/multiple processes (separate OS processes, typically for scaling I/O-bound throughput across CPU cores) — three genuinely different mechanisms, easily conflated under a vague "Node uses more than one thread somehow" framing.

Clarifying questions expected:

  • "Is the actual bottleneck CPU-bound computation, or I/O-bound waiting that merely looks slow?" — Worker Threads specifically address the former; the thread pool/event loop already address the latter.
  • "Does the computation need to communicate large amounts of data back and forth, where copying cost via message passing would itself become significant?" — a real, practical Worker Thread design consideration.

Code / implementation expected: Yes — the real, measured heartbeat contrast (0 ticks blocking the main thread directly vs. 69 ticks with the identical work moved to a Worker Thread) is the concrete, convincing proof of genuine parallelism, not a description of it.

worker threadsconcurrencyperformancecpu-bound
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for Node.js system-design interviews — assumes familiarity with the event loop and the libuv thread pool from their own dedicated questions. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview te

Solution ready — 2 min read

Classified // press E to declassify

04

Read the code

The identical fib(40) computation: freezing the main thread's heartbeat directly vs. letting it tick 69 times via a Worker Thread
// worker.js
const { parentPort } = require("worker_threads");
function fib(n) { return n < 2 ? n : fib(n - 1) + fib(n - 2); }
parentPort.postMessage(fib(40));

// main.js
const { Worker } = require("worker_threads");
let ticks = 0;
setInterval(() => ticks++, 10);

function fib(n) { return n < 2 ? n : fib(n - 1) + fib(n - 2); }
const t0 = Date.now();
fib(40); // SYNCHRONOUS, on the main thread
console.log("main-thread:", Date.now() - t0, "ms; ticks:", ticks);
// main-thread: 963 ms; ticks: 0   <- completely frozen

const ticksBefore = ticks;
const t1 = Date.now();
new Worker("./worker.js").on("message", (result) => {
  console.log("worker:", Date.now() - t1, "ms; MAIN thread ticks:", ticks - ticksBefore);
  // worker: 1001 ms; MAIN thread ticks: 69   <- main thread stayed free
});
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 48 of 152 decoded in the Node.js track. One more won't hurt.

Back to track