Glossary / Node.js child processes

Node.js child processes

Node.js child processes run commands or JavaScript programs in separate operating-system processes. They provide process-level isolation and access to external tools, with communication through standard input/output, IPC, or operating-system pipes. They are more isolated than worker threads but have greater startup and memory costs.

APIs and data flow

spawn() streams stdin, stdout, and stderr and is suited to long-running processes or large output. exec() runs through a shell and buffers output up to a configured limit, making it convenient for small trusted commands but risky for untrusted interpolation. execFile() runs a file directly without a shell by default. fork() starts a Node process with an IPC channel.

import { spawn } from 'node:child_process';
const child = spawn('grep', ['needle', 'input.txt'], { shell: false });
child.stdout.on('data', chunk => consume(chunk));
child.stderr.on('data', chunk => logError(chunk));
child.once('error', error => handleSpawnFailure(error));
child.once('close', (code, signal) => finish(code, signal));

The error event can report a spawn failure; exit reports process termination, while close occurs after stdio streams close and is often the better point to know output is complete. A process may emit both error and close; settle application work exactly once. For exec callbacks, output buffering is bounded by maxBuffer and can terminate the child if exceeded.

Security and lifecycle

Shell command construction with user input can cause command injection. Prefer executable plus argument array and shell: false, validate inputs, and avoid assuming argument escaping is a universal substitute for validation. Child processes inherit selected environment variables and may inherit file descriptors; minimize secrets and permissions. On shutdown, send a graceful signal, wait for a bounded interval, then force termination if policy permits. Killing a parent does not always terminate descendants; process groups and platform behavior matter.

Engineering guidance

Choose streaming APIs for potentially large output, drain or redirect pipes to avoid child blockage, set timeouts, and handle nonzero exit codes as normal outcomes rather than only exceptions. Bound concurrent children. Test missing executable, permission errors, large output, signal termination, and cleanup on parent cancellation.

Interview questions

  1. spawn vs exec? spawn streams; exec invokes a shell and buffers output.
  2. Why avoid shell interpolation? It risks command injection and quoting errors.
  3. exit vs close? close follows stdio closure; output may still be draining after process exit.
  4. What causes deadlock with pipes? If the parent does not consume a full stdout/stderr pipe, the child may block writing.
  5. How to handle large output? Use spawn and stream the output with backpressure-aware handling.

References