Skip to the solution
MediumLow-Level Design7 min read

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

763 views

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-process
  • concurrency
  • performance

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 Β· 10 sections

Read the code

JavaScriptexec'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

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 68 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 implement real-time communication with WebSockets (ws) in Node.js?

Continue