Child Processes & Process Management

5 questions found

What are the four main methods for creating a child process in Node.js, and how do they differ?

Beginner
spawn() launches a command and streams its stdout/stderr incrementally, suited for long-running processes or large output. exec() runs a command through a shell and buffers its entire output into a callback, convenient for short commands with small output but risky for large output (memory) and shell-injection if input isn't sanitized. execFile() is like exec() but runs an executable directly without a shell, avoiding shell-injection risk. fork() is a specialized spawn specifically for running another Node.js script as a child process, automatically setting up an IPC channel for message passing.
const { spawn, exec, execFile, fork } = require('node:child_process');

spawn('ls', ['-la']); // streaming output
exec('ls -la', (err, stdout) => console.log(stdout)); // buffered, via shell
execFile('ls', ['-la'], (err, stdout) => console.log(stdout)); // buffered, no shell
fork('./worker-script.js'); // another Node.js process, with IPC built in
Real-world example A CI tool uses spawn() to stream a long-running build command's output line by line to the console in real time, while a smaller utility script uses execFile() to safely run a fixed executable with user-supplied arguments, avoiding the shell-injection risk that exec() would introduce with unsanitized input.

Common follow-ups: Why is exec() considered risky when any part of the command string comes from user input?;What's the memory implication of exec()'s output-buffering behavior for a command producing gigabytes of output?

Security;CLI Tools & Scripting with Node.js

How does inter-process communication (IPC) work between a parent process and a child created with fork()?

Intermediate
fork() automatically establishes a communication channel between the parent and child, letting both sides send and receive JSON-serializable messages via process.send() (in the child) or child.send() (in the parent), and listen via the 'message' event -- this is a much simpler communication mechanism than what's available with spawn() or exec(), which only expose the child's stdout/stderr/stdin streams rather than a structured message channel.
// parent.js
const { fork } = require('node:child_process');
const child = fork('./child.js');
child.send({ command: 'start', data: [1, 2, 3] });
child.on('message', (msg) => console.log('From child:', msg));

// child.js
process.on('message', (msg) => {
  const result = msg.data.reduce((a, b) => a + b, 0);
  process.send({ result });
});
Real-world example A CPU-intensive report-generation task is offloaded to a forked child process, with the parent sending the report parameters via child.send() and receiving the completed report data back via the 'message' event, keeping the main process's event loop free to handle other requests throughout.

Common follow-ups: What are the serialization limits of what can be sent via process.send(), given messages must be JSON-serializable?;How does IPC-based communication with fork() compare to postMessage() with worker threads in terms of overhead?

Advanced Node.js;Async Patterns

How do you handle a child process that hangs or never terminates, and what's the correct way to enforce a timeout?

Advanced
Both exec() and execFile() support a 'timeout' option that automatically sends a kill signal to the child process if it hasn't completed within the specified duration; for spawn(), which doesn't have a built-in timeout option, you implement this manually with setTimeout() calling child.kill() if the process hasn't emitted its 'exit' event within the allowed time, typically also clearing that timeout if the process finishes normally first.
const { spawn } = require('node:child_process');

const child = spawn('some-long-command');
const timeout = setTimeout(() => {
  child.kill('SIGTERM');
  console.error('Process timed out and was killed');
}, 30000);

child.on('exit', () => clearTimeout(timeout));
Real-world example A code-execution service running user-submitted scripts as child processes enforces a strict 10-second timeout, forcibly killing any script that runs longer, preventing a single infinite-looping or malicious submission from tying up a worker slot indefinitely.

Common follow-ups: What's the difference between SIGTERM and SIGKILL when force-terminating a hung process, and when would you need to escalate from one to the other?;How do you ensure any resources (file handles, network connections) held by the killed child are properly cleaned up?

Security;Error Handling

How would you pipe the output of one child process directly into the input of another, replicating a shell pipeline in Node.js?

Intermediate
Each child process's stdout and stdin are Node.js streams, so you can use .pipe() directly to connect one process's stdout to another's stdin, replicating a shell pipeline like 'cat file.txt | grep pattern' entirely within Node.js code, with all the usual stream backpressure handling applying automatically.
const { spawn } = require('node:child_process');

const cat = spawn('cat', ['large-file.txt']);
const grep = spawn('grep', ['error']);

cat.stdout.pipe(grep.stdin);
grep.stdout.pipe(process.stdout);
Real-world example A log-analysis script pipes a 'cat' process's output through a 'grep' child process to filter for error lines, then pipes that filtered output directly to the parent process's own stdout, replicating a familiar shell pipeline programmatically for use within a larger Node.js automation script.

Common follow-ups: How does backpressure propagate correctly through a multi-stage piped child process chain?;What happens to the downstream process if the upstream process is killed mid-pipeline?

Streams & Buffers;CLI Tools & Scripting with Node.js

What is the 'detached' option when spawning a child process, and when would you use it?

Advanced
By default, a child process is tied to its parent's lifecycle in certain ways depending on the platform -- the { detached: true } option allows the child to continue running independently even after the parent process exits, useful for launching a genuinely independent background process (like starting a separate long-running daemon) that shouldn't be killed just because the process that launched it terminates; combined with child.unref(), the parent can also exit without waiting for the detached child.
const { spawn } = require('node:child_process');

const child = spawn('long-running-daemon', [], {
  detached: true,
  stdio: 'ignore', // don't keep parent alive waiting on child's stdio either
});
child.unref(); // parent process can now exit independently of the child
Real-world example A CLI installer tool spawns a background update-checking process using detached: true and unref(), allowing the installer itself to exit immediately after starting the checker, while the checker continues running independently in the background even after the installer's own process has terminated.

Common follow-ups: What's the difference in behavior of detached processes between Linux/macOS and Windows?;Why is stdio: 'ignore' commonly paired with detached: true, and what happens if it's omitted?

Deployment & Process Managers (PM2);CLI Tools & Scripting with Node.js