Skip to the solution
MediumSystem Design7 min read

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

1.1k views

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 threads
  • concurrency
  • performance
  • cpu-bound

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

7 min read Β· 9 sections

Read the code

JavaScriptThe 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
});

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 48 of 152 in Node.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Node.js questions
Up nextMedium

How do you manage sessions and authentication in a Node.js web application?

Continue