What is the
clustermodule and how does it work?clusterforks copies of the server, usually one per core, sharing one port; the primary hands connections to workers round-robin (except on Windows).const cluster = require('node:cluster'); const http = require('node:http'); const os = require('node:os'); if (cluster.isPrimary) { for (let i = 0; i < os.availableParallelism(); i++) cluster.fork(); cluster.on('exit', (worker, code) => { console.log(`worker ${worker.process.pid} exited with ${code}, starting a new one`); cluster.fork(); }); } else { http.createServer((req, res) => res.end(`handled by ${process.pid}\n`)).listen(3000); }Workers share no memory, so sessions and caches move to Redis. In containers the usual choice is one process per container instead.
What are worker threads in Node.js, and when do you use them?
worker_threadsruns JavaScript in parallel threads in one process, each with its own V8 isolate and event loop, talking by messages or aSharedArrayBuffer. Use them for CPU-heavy work.const { Worker, isMainThread, parentPort, workerData } = require('node:worker_threads'); function fib(n) { return n < 2 ? n : fib(n - 1) + fib(n - 2); } if (isMainThread) { const worker = new Worker(__filename, { workerData: 35 }); worker.on('message', (result) => console.log('fib(35) =', result)); worker.on('error', (err) => console.error(err)); console.log('main thread is still free'); } else { parentPort.postMessage(fib(workerData)); } // Node.js prints: // main thread is still free // fib(35) = 9227465Not for I/O, which is already async. Keep a fixed pool (
piscina) rather than one worker per request.What is the difference between
cluster,worker_threadsandchild_process?clusterworker_threadschild_processUnit process thread process Memory separate separate heaps, optional SharedArrayBufferseparate Use for all cores for HTTP CPU-heavy JavaScript other programs ( ffmpeg,git)A thread is cheaper than a process, but a native crash in a worker kills the whole process, while a crashed child leaves the parent running.
What is the difference between
spawn,exec,execFileandfork?Function Shell Output Use for spawnno (by default) streams long processes, large output execyes buffered (1 MB default) short shell commands execFileno buffered a program with arguments forkno stdio plus IPC a Node.js script you message exec('convert ' + userFile)is command injection. Pass user input as an argument array toexecFileorspawn.How do you handle a CPU-intensive task in a Node.js API?
Get it off the event loop: a job queue (BullMQ, SQS) when the client can wait, replying
202with a job id; a worker thread pool when the response needs the result; or chunking withsetImmediateas a last resort. The wrong answer isasync: wrapping a synchronous loop in anasyncfunction does not move it to another thread.How do you find out why a Node.js service is slow?
Measure, then tell the kinds of slow apart:
- Event loop blocked (every endpoint slow, one core at 100%): CPU profile with
node --cpu-profor--inspect, read the flame graph. - Waiting on I/O (CPU low): time database and downstream calls, check pool limits, including the libuv pool.
- Memory pressure (RSS rising, latency spikes): GC pauses; use heap snapshots.
perf_hooks.monitorEventLoopDelay()exported as a metric shows a blocked loop.- Event loop blocked (every endpoint slow, one core at 100%): CPU profile with
What is the V8 heap limit in Node.js and how do you change it?
V8 caps the old-generation heap, and past it the process dies with "JavaScript heap out of memory". Since Node 12 the default depends on available memory; read it from
v8.getHeapStatistics().heap_size_limitand change it with--max-old-space-size=4096(MiB), below the container limit. If usage grows without end it is a leak, and a bigger heap only delays the crash.How would you add caching to a Node.js API?
Pick by scope: an in-process LRU
Mapfor small hot data (each process has its own copy), Redis for anything shared across instances, and HTTP caching (Cache-Control, a CDN) for public GET responses. Two traps: an unboundedMapcache is a memory leak, so cap it and expire entries; and when a hot key expires, many requests recompute it at once, so cache the in-flight promise.What is PM2, and why run Node.js under a process manager?
PM2 is a process manager for Node.js: it restarts the app when it crashes, runs one instance per core in cluster mode, reloads workers one by one for zero-downtime deploys, collects logs and starts the app again after a reboot.
pm2 start server.js -i max --name api # one process per core pm2 reload api # restart workers one at a time pm2 logs api pm2 startup && pm2 save # start again after a rebootIn Docker or Kubernetes the orchestrator already restarts and scales containers, so run plain
node, one process per container, instead of PM2.Why use a database connection pool in Node.js, and how big should it be?
Opening a connection costs a TCP, TLS and login round trip, and one connection runs one query at a time. A pool keeps a few connections open and lends one per query.
const { Pool } = require('pg'); const pool = new Pool({ max: 10 }); const { rows } = await pool.query('SELECT * FROM users WHERE id = $1', [id]); const client = await pool.connect(); // a transaction needs one connection try { await client.query('BEGIN'); await client.query('UPDATE accounts SET balance = balance - 100 WHERE id = $1', [from]); await client.query('COMMIT'); } catch (err) { await client.query('ROLLBACK'); throw err; } finally { client.release(); // forget this and the pool runs dry }Size it small: pool size times instances must stay under the database's limit (PostgreSQL's
max_connectionsdefaults to 100), and more connections than the database has cores rarely helps.
Node.js Performance and Scaling Interview Questions
Using every CPU core and keeping CPU-heavy work off the event loop.
10 questions