Skip to solution
mediumLow-Level Design

Explain the differences between fork(), spawn(), and exec() in the child_process module.

763 views
01

Understand the problem

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 its stdout/stderr as 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: an exec() call using && worked and returned the complete, concatenated output in one callback invocation.
  • fork() is specifically a specialized spawn() for launching another Node.js module, with one crucial addition: it automatically sets up a dedicated IPC (inter-process communication) channel, enabling child.send()/process.send() and 'message' events for structured message passing between parent and child — 📌 verified directly: a real message round-trip worked through fork()'s IPC, while an identical check on a plain spawn()'d child confirmed child.send is genuinely undefined there.
  • exec()'s buffering has a real, practical limit: a maxBuffer option (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() (or spawn() 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-enabled spawn().

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.

child-processconcurrencyperformance
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 interviews — assumes basic child_process familiarity. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. All three functions below were actually run on Node v24.1

Solution ready — 2 min read

Classified // press E to declassify

04

Read the code

exec's shell-and-buffer behavior, spawn's streaming, and fork's real IPC round-trip versus spawn's genuinely absent .send
const { exec, spawn, fork } = require("child_process");

// exec: shell operators work directly; output buffered into one callback
exec("echo hello from exec && echo world", (err, stdout) => {
  console.log(JSON.stringify(stdout)); // "hello from exec \r\nworld\r\n"
});

// spawn: no shell, output streamed via events
const child = spawn(process.execPath, ["-e", 'console.log("hello from spawn")']);
let spawnOut = "";
child.stdout.on("data", (d) => (spawnOut += d));
child.on("close", () => console.log(JSON.stringify(spawnOut))); // "hello from spawn\n"

// fork: spawn() for a Node module, PLUS a real IPC channel
// fork-child.js: process.on("message", m => process.send({ reply: `got: ${m}` }));
const forked = fork("fork-child.js");
forked.on("message", (msg) => console.log(JSON.stringify(msg))); // { reply: "got: hello" }
forked.send("hello");

// Confirming spawn() genuinely has no IPC:
const plain = spawn(process.execPath, ["-e", "1"]);
console.log(typeof plain.send); // undefined
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 68 of 152 decoded in the Node.js track. One more won't hurt.

Back to track