Common Node.js bugs and gotchas — a field reference
Every bug in this lesson has already been explained mechanically somewhere earlier in this domain — this is the field-reference version, the shape each one actually takes in real code, so it's recognizable on sight instead of requiring the mechanism to be re-derived from scratch every time.
4 min read
Bug 1: a synchronous call blocking every other request
app.get("/report", (req, res) => {
const data = fs.readFileSync("./huge-report.csv"); // blocks EVERY other request on the server
res.send(data);
});Covered mechanically in the fs-module lesson: readFileSync blocks the single thread for its entire duration — no other request, no matter how simple, can even begin being processed until it finishes. The fix: the async fs.readFile or fs/promises form, always, for anything running inside a server's request path.
Bug 2: a forgotten next() hanging a request forever
app.use((req, res, next) => {
console.log("logging the request");
// missing next() — the request just hangs, no timeout, no error
});Covered mechanically in the middleware-chains lesson: nothing advances the chain automatically — a middleware that does its work but forgets next() (and doesn't send a response itself) leaves the request stuck indefinitely. The fix: audit every middleware's code paths, especially early-return branches inside conditionals, for a missing next() call.
Bug 3: assuming in-memory state is shared across cluster workers
const cache = new Map(); // in a CLUSTERED app, EVERY worker has its OWN separate Map
app.get("/data", (req, res) => {
if (cache.has(req.query.key)) return res.json(cache.get(req.query.key)); // only hits if THIS worker cached it
});Covered mechanically in the clustering lesson: each cluster worker is a genuinely separate OS process, sharing no memory at all — a value cached by worker 2 simply doesn't exist in worker 1's memory. The fix: shared, cross-worker state (sessions, caches) needs to live somewhere genuinely external — Redis, a real database — not in any individual worker's own process memory.
Bug 4: a request cut off mid-response during a routine deploy
// No SIGTERM handler at all — the default behavior on receiving SIGTERM
// is to exit close to immediately, dropping whatever's in flightCovered mechanically in the graceful-shutdown lesson: without an explicit SIGTERM handler, a deploy or scale-down kills the process mid-request by default, a real, user-visible failure during what should be routine. The fix: server.close() to stop accepting new connections while letting in-flight ones finish, with a bounded force-exit timeout as a safety net against a hang.
Bug 5: exec's output buffer silently truncating (or erroring on) large output
exec("find / -type f", (err, stdout) => { /* output can EXCEED exec's default buffer limit */ });Covered mechanically in the child-processes lesson: exec buffers the entire output in memory before calling back, and has a real, default maximum buffer size — a command producing more output than that limit causes an error. The fix: spawn, which streams output chunk by chunk with no such buffer ceiling, for any command with genuinely large or unbounded output.
Bug 6: a CPU-heavy computation freezing the entire server
app.get("/analyze", (req, res) => {
const result = runExpensiveAnalysis(req.body); // BLOCKS the event loop — every other request waits
res.json(result);
});Covered mechanically in the worker-threads lesson: non-blocking I/O never helps genuinely CPU-bound work, since there's no idle waiting period to reclaim — the single thread stays busy the entire computation. The fix: offload the heavy computation to a worker_threads worker, freeing the main thread to keep handling other (I/O-bound) requests concurrently.
The actual throughline across all six
Every one of these traces back to the same handful of mechanisms this domain already covered in depth: Node's single-threaded event loop being genuinely blockable by synchronous or CPU-heavy work, the middleware chain's explicit next()-driven execution, and cluster workers' complete lack of shared memory. Recognizing a bug's shape on sight — "this smells like a blocked event loop," "this smells like cross-worker state that doesn't actually exist" — is what separates fixing a Node.js production issue quickly from re-deriving these mechanisms from first principles every single time one shows up.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. Why does calling `fs.readFileSync()` inside a request handler block every OTHER incoming request, not just the current one?
2. Why does an in-memory cache work fine in a single-process app but fail confusingly once the app is clustered?
3. What's the actual throughline connecting all six bugs in this field-reference lesson?