Question presented to candidate: "You need to run another Node.js script and exchange structured messages with it, not just capture its console output. Which of fork(), spawn(), or exec() is actually built for that, and what specifically makes it different?"
What a strong answer should cover:
spawn()launches any external command as a child process, streaming itsstdout/stderras event-emitting streams — the general-purpose primitive, no shell involved by default, well-suited to large or long-running output.exec()also launches a command, but runs it through a shell (so shell operators like&&/|/glob patterns work directly in the command string) and buffers the entire output into memory, delivered all at once via a callback — 📌 verified directly: anexec()call using&&worked and returned the complete, concatenated output in one callback invocation.fork()is specifically a specializedspawn()for launching another Node.js module, with one crucial addition: it automatically sets up a dedicated IPC (inter-process communication) channel, enablingchild.send()/process.send()and'message'events for structured message passing between parent and child — 📌 verified directly: a real message round-trip worked throughfork()'s IPC, while an identical check on a plainspawn()'d child confirmedchild.sendis genuinelyundefinedthere.exec()'s buffering has a real, practical limit: amaxBufferoption (defaulting to a few megabytes) that, if exceeded by the command's actual output, causes the call to error out rather than silently truncating — a common, real gotcha for a command producing more output than expected.- A precise answer maps each to its actual use case:
exec()for a short-lived command with small, complete output where shell syntax is genuinely convenient;spawn()for a long-running process or large/streamed output;fork()specifically for spawning another Node.js process you need to exchange structured messages with, such as a worker process handling CPU-bound work outside the main event loop (a process-based alternative to Worker Threads, covered in its own dedicated question). - Running a shell command via
exec()(orspawn()with{ shell: true }) with any user-controlled input concatenated into the command string is a real, serious command-injection risk — a precise answer names this alongside the mechanical differences, not just as an unrelated security footnote.
Clarifying questions expected:
- "Does the child process need structured message exchange, or just captured output?" — the deciding question between
fork()and the other two. - "Is any part of the command string derived from user input?" — a real security concern specifically for
exec()/shell-enabledspawn().
Code / implementation expected: Yes — actually running all three and observing exec's shell-and-buffer behavior, spawn's streamed output, and fork's real IPC round-trip (versus spawn's genuinely absent .send()) is the concrete, convincing demonstration.