Debugging Node.js
Debugging is the systematic process of finding why code behaves differently than you expect — and Node gives you far more than console.log. There's a full V8 debugger built into the runtime, breakpoint debugging in VS Code and Chrome DevTools, and diagnostic tooling for crashes and performance. This page sets the foundation: a disciplined debugging method, the spectrum of techniques from logging to breakpoints, why console.log debugging hits a wall, and the categories of bug you'll meet in Node — including the async-specific ones that trip everyone up.
A method, not random poking
Reproduce it reliably — a bug you can't trigger on demand, you can't fix with confidence. Find the minimal input that reproduces it.
Form a hypothesis — what do you think is wrong and why? Debugging is the scientific method, not guesswork.
Observe the actual state — inspect variables, arguments, and control flow at the point of failure; don't assume.
Isolate — narrow the failing code by bisecting: comment out, add checks, or
git bisectacross commits.Fix the root cause, not the symptom — a patch that hides the bad value leaves the real bug in place.
Add a regression test — reproduce the bug as a failing test first, so it can never silently return.
The debugging toolkit — from cheap to powerful
Technique | Best for | Limitation |
|---|---|---|
| Quick, one-off checks | No history, clutters code, requires re-runs to add more |
| Inspecting deep/nested objects | Still manual logging |
Built-in debugger ( | Breakpoints from the terminal | CLI ergonomics are basic |
Inspector + DevTools/VS Code | Full breakpoint debugging, watch, call stack | Setup needed; not for prod by default |
Diagnostic report / heap snapshot | Crashes, leaks, production incidents | Post-hoc analysis, not live stepping |
Why console.log debugging hits a wall
// The console.log spiral — add a log, re-run, add another, re-run...
function processOrder(order) {
console.log('order:', order) // is the input right?
const total = calculateTotal(order.items)
console.log('total:', total) // is the total right?
const tax = total * 0.2
console.log('tax:', tax) // ...and so on, forever
return total + tax
}Better than bare console.log when you must log
import util from 'node:util'
const obj = { user: { id: 1, roles: ['admin'] }, nested: { deep: { value: 42 } } }
// console.log truncates deep objects to [Object]:
console.log(obj) // { user: ..., nested: { deep: [Object] } }
// util.inspect shows full depth + colour:
console.log(util.inspect(obj, { depth: null, colors: true }))
// console.table for arrays of objects:
console.table([{ id: 1, name: 'Ada' }, { id: 2, name: 'Linus' }])
// console.trace prints the call stack to here:
console.trace('how did we get here?')
// Time a block:
console.time('query'); await db.query(sql); console.timeEnd('query') // query: 12.4msCategories of Node bug
Category | Typical cause | Tool |
|---|---|---|
Logic errors | Wrong condition, off-by-one, bad assumption | Breakpoints, step-through |
Async bugs | Missing | Async stack traces, careful logging |
Crashes | Uncaught exception, out-of-memory | Diagnostic report, core dumps |
Memory leaks | Retained references, growing caches | |
Performance | Blocked event loop, slow queries | Profiling, flame graphs |