Child Processes & Worker Threads

📖 Concept

Node.js provides two mechanisms for parallel execution: Child Processes (separate OS processes) and Worker Threads (threads within the same process). Choose based on your use case.

Child Processes (child_process module): Spawn separate OS processes to run system commands, scripts, or other programs.

Method Use Case
exec(cmd) Run shell commands, get output as string (buffers stdout)
execFile(file, args) Run a specific executable (faster, no shell)
spawn(cmd, args) Stream-based for long-running processes
fork(modulePath) Spawn a new Node.js process with IPC channel

Worker Threads (worker_threads module): Run JavaScript in parallel threads within the same process. Unlike child processes, workers share memory via SharedArrayBuffer.

Feature Child Process Worker Thread
Isolation Full (separate process) Partial (same process)
Communication IPC (serialized messages) MessagePort + SharedArrayBuffer
Memory Separate (duplicated) Shared possible
Overhead High (new V8 instance + OS process) Lower (new V8 isolate)
Best for System commands, CPU isolation CPU-heavy JS computations

When to use which:

  • Child process: Running shell commands, Python scripts, system tools, process isolation for security
  • Worker thread: CPU-intensive JavaScript (image processing, crypto, data parsing)
  • Neither: I/O-bound work (use async I/O — the event loop handles it perfectly)

🏠 Real-world analogy: Child processes are like hiring a separate contractor — they work independently with their own tools. Worker threads are like assigning a colleague — they share office resources but work on different tasks simultaneously.

💻 Code Example

codeTap to expand ⛶
1// Child Processes & Worker Threads
2
3const { exec, execFile, spawn, fork } = require("child_process");
4const { Worker, isMainThread, parentPort, workerData } = require("worker_threads");
5const { promisify } = require("util");
6const execAsync = promisify(exec);
7
8// === CHILD PROCESSES ===
9
10// 1. exec — Run shell commands (output buffered)
11async function runShellCommand() {
12 try {
13 const { stdout, stderr } = await execAsync("ls -la && echo 'Done!'");
14 console.log("Output:", stdout);
15 if (stderr) console.error("Stderr:", stderr);
16 } catch (err) {
17 console.error("Command failed:", err.message);
18 }
19}
20
21// 2. spawn — Stream-based (for long-running commands)
22function runLongCommand() {
23 const child = spawn("find", [".", "-name", "*.js", "-type", "f"]);
24
25 child.stdout.on("data", (data) => {
26 console.log("Found:", data.toString().trim());
27 });
28
29 child.stderr.on("data", (data) => {
30 console.error("Error:", data.toString());
31 });
32
33 child.on("close", (code) => {
34 console.log(`Process exited with code ${code}`);
35 });
36}
37
38// 3. fork — Spawn Node.js process with IPC
39// === parent.js ===
40function forkWorker() {
41 const worker = fork("./heavy-computation.js");
42
43 worker.send({ type: "start", data: { numbers: [1, 2, 3, 4, 5] } });
44
45 worker.on("message", (result) => {
46 console.log("Result from worker:", result);
47 });
48
49 worker.on("exit", (code) => {
50 console.log(`Worker exited with code ${code}`);
51 });
52}
53
54// === heavy-computation.js ===
55// process.on("message", (msg) => {
56// if (msg.type === "start") {
57// const result = msg.data.numbers.reduce((a, b) => a + b, 0);
58// process.send({ type: "result", value: result });
59// process.exit(0);
60// }
61// });
62
63// === WORKER THREADS ===
64
65// 4. Basic Worker Thread
66if (isMainThread) {
67 // Main thread
68 function createWorker(data) {
69 return new Promise((resolve, reject) => {
70 const worker = new Worker(__filename, { workerData: data });
71
72 worker.on("message", resolve);
73 worker.on("error", reject);
74 worker.on("exit", (code) => {
75 if (code !== 0) {
76 reject(new Error(`Worker stopped with exit code ${code}`));
77 }
78 });
79 });
80 }
81
82 // Run CPU-intensive work in parallel
83 async function parallelComputation() {
84 const tasks = [
85 { start: 0, end: 25000000 },
86 { start: 25000000, end: 50000000 },
87 { start: 50000000, end: 75000000 },
88 { start: 75000000, end: 100000000 },
89 ];
90
91 console.time("parallel");
92 const results = await Promise.all(tasks.map(createWorker));
93 const total = results.reduce((a, b) => a + b, 0);
94 console.timeEnd("parallel");
95 console.log("Sum:", total);
96 }
97
98 // Compare with single-threaded
99 async function singleThreaded() {
100 console.time("single");
101 let sum = 0;
102 for (let i = 0; i < 100000000; i++) sum += i;
103 console.timeEnd("single");
104 console.log("Sum:", sum);
105 }
106} else {
107 // Worker thread
108 const { start, end } = workerData;
109 let sum = 0;
110 for (let i = start; i < end; i++) sum += i;
111 parentPort.postMessage(sum);
112}
113
114// 5. Worker pool pattern
115class WorkerPool {
116 constructor(workerScript, poolSize) {
117 this.workerScript = workerScript;
118 this.poolSize = poolSize;
119 this.workers = [];
120 this.queue = [];
121 this.activeWorkers = 0;
122 }
123
124 execute(data) {
125 return new Promise((resolve, reject) => {
126 this.queue.push({ data, resolve, reject });
127 this.processQueue();
128 });
129 }
130
131 processQueue() {
132 while (this.queue.length > 0 && this.activeWorkers < this.poolSize) {
133 const { data, resolve, reject } = this.queue.shift();
134 this.activeWorkers++;
135
136 const worker = new Worker(this.workerScript, { workerData: data });
137
138 worker.on("message", (result) => {
139 this.activeWorkers--;
140 resolve(result);
141 this.processQueue();
142 });
143
144 worker.on("error", (err) => {
145 this.activeWorkers--;
146 reject(err);
147 this.processQueue();
148 });
149 }
150 }
151}
152
153// Usage:
154// const pool = new WorkerPool("./worker.js", 4); // 4 worker threads
155// const results = await Promise.all([
156// pool.execute({ task: "process-image", path: "photo1.jpg" }),
157// pool.execute({ task: "process-image", path: "photo2.jpg" }),
158// pool.execute({ task: "process-image", path: "photo3.jpg" }),
159// ]);

🏋️ Practice Exercise

Exercises:

  1. Use exec to run a system command and capture the output — handle both success and failure
  2. Use spawn to run a long-running command and stream its output in real-time
  3. Create a forked Node.js process that performs heavy computation and sends results back via IPC
  4. Implement a Worker Thread that computes prime numbers in parallel across 4 threads
  5. Build a Worker Pool that reuses a fixed number of threads for queued tasks
  6. Compare the performance of single-threaded vs Worker Thread computation for a CPU-intensive task

⚠️ Common Mistakes

  • Using exec() with user input without sanitization — this is a shell injection vulnerability; use execFile() or spawn() with arguments array instead

  • Using Worker Threads for I/O-bound tasks — the event loop already handles async I/O efficiently; Workers add overhead without benefit for I/O work

  • Not handling the 'error' event on child processes — errors in spawned processes crash silently if not caught

  • Creating too many Worker Threads — each thread has V8 overhead (~2-5MB); creating hundreds wastes memory; use a worker pool with a fixed size

  • Using exec() for commands with large output — exec buffers ALL output in memory; use spawn() with streaming for large outputs

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for Child Processes & Worker Threads. Login to unlock this feature.