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/awaitor 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
asyncdoes not fix it — a synchronous, CPU-bound computation blocks the main thread identically whether called directly or from inside anasyncfunction, verified directly: runningfib(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 aSharedArrayBufferfor 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 aSharedArrayBuffer. - 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.