Skip to content

Node.js Child Process Deep Dive — Complete Guide to spawn, exec, and fork

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Node.js Child Process Deep Dive. We cover key concepts, practical examples, and best practices to help you master this topic.

Node.js child process deep dive covers the four methods for creating subprocesses: spawn (streamed), exec (buffered), fork (IPC), and execFile (binary), with stdio and error handling.

What You'll Learn

By the end of this tutorial, you'll use spawn for streaming output, exec for buffered results, fork for inter-process communication, manage child process lifecycles, and handle errors gracefully.

Why Child Processes Matter

Child processes offload CPU-intensive work, run system commands, and enable multi-process architectures. They are essential for image processing, video encoding, and Shell Script integration.

Real-World Use

A thumbnail generator spawns ImageMagick processes for each image, limits concurrent processes to CPU count, collects results via stdout, and kills hanging processes after timeout.

Child Process Path

flowchart LR
  A[Architecture] --> B[Child Process Deep]
  B --> C[Worker Threads]
  B --> D[Cluster Module]
  B --> E[PM2]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

Spawning Processes

spawn streams output, uses less memory for large data, and is the preferred method for most cases.

const { spawn } = require("node:child_process");
const ls = spawn("ls", ["-la", "/tmp"]);
ls.stdout.on("data", (data) => {
  console.log(`stdout: ${data}`);
});
ls.stderr.on("data", (data) => {
  console.error(`stderr: ${data}`);
});
ls.on("close", (code) => {
  console.log(`Process exited with code ${code}`);
});

Exec for Buffered Output

exec buffers all output in memory. Use for short commands with small output.

const { exec } = require("node:child_process");
exec("du -sh /tmp", { maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
  if (err) {
    console.error("Error:", err.message);
    return;
  }
  console.log("Disk usage:", stdout.trim());
});

Forked Processes with IPC

fork creates a Node.js subprocess with IPC channel for message passing.

// parent.js
const { fork } = require("node:child_process");
const child = fork("./worker.js");
child.send({ task: "compute", data: { iterations: 1000000 } });
child.on("message", (result) => {
  console.log("Result from child:", result);
  child.kill();
});
child.on("exit", (code) => {
  console.log("Child exited with code:", code);
});

stdio Configuration

Control stdin, stdout, stderr streams with the stdio option.

const { spawn } = require("node:child_process");
const child = spawn("wc", ["-w"], {
  stdio: ["pipe", process.stdout, "pipe"],
});
child.stdin.write("Hello world from child process");
child.stdin.end();
child.on("close", (code) => {
  console.log("\nDone with code:", code);
});

Process Pools

Manage a pool of child processes for concurrent task execution.

class ProcessPool {
  constructor(maxProcesses = require("node:os").cpus().length) {
    this.max = maxProcesses;
    this.active = 0;
    this.queue = [];
  }
  spawn(modulePath, args = []) {
    return new Promise((resolve, reject) => {
      const run = () => {
        this.active++;
        const child = require("node:child_process").fork(modulePath);
        child.send({ args });
        child.on("message", (result) => {
          this.active--;
          this.processQueue();
          resolve(result);
        });
        child.on("error", (err) => {
          this.active--;
          this.processQueue();
          reject(err);
        });
      };
      if (this.active >= this.max) {
        this.queue.push(run);
      } else {
        run();
      }
    });
  }
  processQueue() {
    if (this.queue.length > 0 && this.active < this.max) {
      const next = this.queue.shift();
      next();
    }
  }
}

Common Mistakes

1. Not Handling Large stdout with exec

exec buffers all output. For large output, use spawn with streaming.

2. Ignoring Process Exit Codes

A non-zero exit code indicates failure. Always check exit codes.

3. Leaving Zombie Processes

Unattached child processes become zombies. Ensure proper cleanup on parent exit.

4. Security Issues with exec

exec runs commands through a shell. Shell metacharacters in arguments cause Command Injection. Use spawn with args array.

5. Not Setting Timeouts

Hanging child processes never terminate. Set timeout options or kill after a deadline.

Practice Questions

1. When should you use spawn vs exec?

spawn for streaming large output and binary data. exec for short commands with small output needing the result in a callback.

2. How does fork differ from spawn?

fork creates a Node.js process with IPC channel. spawn runs any executable.

3. What is the risk of command injection with exec?

exec runs commands through a shell. User input with semicolons or pipes executes additional commands.

4. How do you kill a hanging child process?

Call child.kill(signal) or set killSignal option. SIGTERM is default.

5. Challenge: Create a process pool that limits concurrent child processes.

class LimitedPool {
  constructor(limit) {
    this.limit = limit;
    this.active = 0;
    this.queue = [];
  }
  exec(cmd, args) {
    return new Promise((resolve, reject) => {
      const run = () => {
        this.active++;
        const proc = require("child_process").spawn(cmd, args);
        let out = "";
        proc.stdout.on("data", (d) => out += d);
        proc.on("close", (code) => { this.active--; this.next(); resolve(out); });
        proc.on("error", reject);
      };
      this.active < this.limit ? run() : this.queue.push(run);
    });
  }
  next() { if (this.queue.length && this.active < this.limit) this.queue.shift()(); }
}

FAQ

What is the difference between child_process and worker_threads?

child_process runs separate processes with separate memory. worker_threads runs threads sharing the same process.

Can I pass environment variables to child processes?

Yes. Use env option in spawn/exec options object. Defaults to process.env.

How do I detach a child process?

Set detached: true and options.stdio: 'ignore'. The child runs independently of the parent.

What is the default shell used by exec?

/bin/sh on Unix, cmd.exe on Windows. Override with shell option.

How do I communicate between parent and child?

fork provides IPC with send/on(message). For spawn, use stdin/stdout piping.

Mini Project: Command Runner with Timeout

Build a safe command execution utility with timeout and output limits.

const { spawn } = require("node:child_process");
class SafeCommandRunner {
  run(command, args = [], options = {}) {
    const timeout = options.timeout || 30000;
    const maxOutput = options.maxOutput || 1048576;
    return new Promise((resolve, reject) => {
      const child = spawn(command, args, { timeout });
      let stdout = "";
      let stderr = "";
      child.stdout.on("data", (d) => { stdout += d; if (stdout.length > maxOutput) child.kill(); });
      child.stderr.on("data", (d) => { stderr += d; });
      child.on("close", (code) => resolve({ code, stdout, stderr, timedOut: false }));
      child.on("error", reject);
      child.on("timeout", () => { child.kill(); resolve({ code: null, stdout, stderr, timedOut: true }); });
    });
  }
}

What's Next

Node.js Cluster Module Node.js Worker Threads Node.js PM2

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro