Debugging & Diagnostics

5 questions found

How do you use the built-in 'debugger' statement together with Node's --inspect flag to set breakpoints in code?

Beginner
Placing a 'debugger;' statement directly in code creates a programmatic breakpoint -- when the script is run with 'node --inspect-brk' and a debugger client (Chrome DevTools or VS Code) is attached, execution pauses at that exact line, letting you inspect variable values, step through subsequent lines one at a time, and evaluate expressions in the current scope before resuming.
function calculateTotal(items) {
  debugger; // execution pauses here when run with --inspect-brk and a debugger attached
  return items.reduce((sum, item) => sum + item.price, 0);
}

// node --inspect-brk app.js, then open chrome://inspect
Real-world example A developer debugging an incorrect total calculation adds a 'debugger;' statement right before the reduce() call, runs the script with --inspect-brk, and steps through each iteration in Chrome DevTools to discover one item's price was unexpectedly undefined rather than a number.

Common follow-ups: What's the difference between --inspect and --inspect-brk in terms of when execution first pauses?;How would you achieve the same breakpoint without modifying the source code, using DevTools' own breakpoint UI instead?

Advanced Node.js;Testing with Jest Mocha & the Node Test Runner

How do you use Node's built-in heap snapshot capability to diagnose a suspected memory leak?

Intermediate
Node.js can generate a heap snapshot (a complete picture of all objects currently in memory and their retaining relationships) via the --heapsnapshot-signal flag, the inspector's Memory panel in Chrome DevTools, or programmatically via the v8 module -- comparing two snapshots taken minutes apart, focusing on which object types have grown significantly in count, reveals what's accumulating and, via the retainer graph, why those objects are still reachable and therefore not being garbage collected.
const v8 = require('node:v8');
const fs = require('node:fs');

function takeHeapSnapshot() {
  const snapshotStream = v8.getHeapSnapshot();
  const fileStream = fs.createWriteStream(`heap-${Date.now()}.heapsnapshot`);
  snapshotStream.pipe(fileStream);
}
// Load the resulting .heapsnapshot file into Chrome DevTools' Memory panel
Real-world example A production service with steadily climbing memory usage has two heap snapshots taken ten minutes apart compared in Chrome DevTools, revealing a rapidly growing count of retained EventEmitter listener objects, pointing directly at the specific class where listeners were being added without ever being removed.

Common follow-ups: How do you interpret the 'retainers' view in a heap snapshot comparison to trace back to the actual leaking code?;What's the overhead of taking a heap snapshot on a live production process, and how do you minimize its impact?

Memory Management & Garbage Collection;Advanced Node.js

What is source map support, and why is it important when debugging a transpiled or bundled Node.js application (like TypeScript compiled to JavaScript)?

Intermediate
A source map maps locations in generated/compiled code back to the corresponding locations in the original source code -- without it, stack traces and debugger breakpoints for a TypeScript or bundled application would point to confusing, unreadable positions in the compiled JavaScript output rather than the actual TypeScript source lines a developer wrote and needs to debug against.
// tsconfig.json enabling source maps
{ "compilerOptions": { "sourceMap": true } }

// Node.js needs to be told to use them for stack traces
// node --enable-source-maps dist/app.js
Real-world example A team debugging a production error stack trace pointing to 'dist/app.js:1:48291' (an unreadable, minified location) enables --enable-source-maps, immediately getting stack traces that instead point to the original readable TypeScript file and line number where the error actually occurred.

Common follow-ups: What's the performance cost, if any, of enabling source maps in a production environment?;How do source maps work when an error occurs in a bundled, minified file with code from many different original source files combined together?

TS: tsconfig & Compiler Options;Error Handling

How would you use the 'clinic.js' toolset (clinic doctor, clinic flame) to diagnose a Node.js performance problem?

Advanced
clinic doctor runs an application under load and produces a diagnostic report highlighting likely categories of problems (event loop blocking, I/O bottlenecks, memory issues) based on observed metrics, pointing you toward which specific tool to use next. clinic flame generates a flame graph -- a visualization of where CPU time is actually being spent across the call stack -- making it straightforward to visually identify which specific function is consuming a disproportionate share of CPU time during a profiling run.
npm install -g clinic

clinic doctor -- node server.js
# generates a report suggesting likely problem categories

clinic flame -- node server.js
# generates an interactive flame graph after the app is exercised under load
Real-world example A team investigating unexplained latency spikes runs clinic doctor against their API under simulated load, which flags 'Event Loop' as the likely culprit; following up with clinic flame reveals a specific synchronous JSON.parse() call on very large payloads as the actual function consuming the disproportionate CPU time blocking the event loop.

Common follow-ups: How do you interpret a flame graph's width versus its depth in terms of what each represents?;What's the difference between clinic's diagnostic categories and what a raw CPU profile alone would tell you?

Performance Optimization & Profiling;Event Loop & Non-blocking IO

What is the DEBUG environment variable convention (popularized by the 'debug' npm package), and how does it let you enable verbose logging selectively?

Intermediate
The 'debug' package lets library and application code create namespaced debug loggers that are silent by default, only producing output when the DEBUG environment variable is set to match their namespace (supporting wildcards) -- this lets a developer enable exactly the verbose logging they need for a specific subsystem (like DEBUG=app:database) without being flooded by unrelated debug output from every other part of the application or its dependencies.
const debug = require('debug')('app:database');

function query(sql) {
  debug('Executing query: %s', sql); // silent unless DEBUG matches 'app:database'
  return db.execute(sql);
}

// DEBUG=app:database node server.js   -- shows only database debug logs
// DEBUG=app:*        node server.js   -- shows all app namespaces
// DEBUG=*             node server.js   -- shows everything, including dependencies using 'debug'
Real-world example A developer debugging a specific slow database query sets DEBUG=app:database before starting the server, getting detailed query-timing debug output only from the database layer, without being overwhelmed by verbose debug logs from Express's own internal routing, which also uses the same 'debug' package convention under a different namespace.

Common follow-ups: How does the 'debug' package achieve near-zero overhead when a given namespace isn't enabled?;Why is many popular npm libraries (like Express) already instrumented with 'debug' namespaces you can enable without any code changes of your own?

Logging & Monitoring;Environment Variables & Configuration