Debugging Techniques — Cheat Sheet

Tags: debugging, troubleshooting, profiling, logs, tracing, devtools, testing

Level: intermediate

TL;DR: Systematic debugging isolates failures by reproducing errors, inspecting state, and verifying hypotheses methodically instead of guessing.

Key concepts

Reproduce the failure consistently with minimal steps before changing any code.

Isolate the defect using binary search across execution paths or git bisect.

Formulate a testable hypothesis before inspecting logs, breakpoints, or memory state.

Change only one variable or configuration parameter at a time.

Verify assumptions about input arguments, environment state, and return types explicitly.

Use logs for asynchronous flows, distributed systems, and non-reproducible race conditions.

Attach interactive debuggers to inspect call stacks, frame scopes, and heap memory.

Monitor network payloads, HTTP status codes, and latency profiles to isolate boundary issues.

Convert each identified and resolved bug into an automated regression test.

Typical questions and ready answers

Q: How do you approach an intermittent or flaky bug in production?R: I first aggregate structured logs and telemetry around timestamps to isolate patterns across environment, concurrency, or payload attributes. Then, I write a reproduction script stressing suspected race conditions or load factors before applying fixes.

Q: What is your strategy when standard logging does not reveal the root cause?R: I form explicit hypotheses and introduce targeted tracepoints or conditional breakpoints near suspect boundaries. I verify data invariant violations step by step rather than scattering random log statements.

Q: When do you prefer an interactive debugger over structured logging?R: I use interactive debuggers locally for complex synchronous logic to inspect closures, call stacks, and mutations in real time. For distributed services or asynchronous race conditions, I rely on structured logs and distributed tracing.

Pitfalls and common mistakes

Fixing symptoms with ad-hoc null checks instead of understanding the root cause.

Modifying multiple lines or configs simultaneously, obscuring what solved or caused problems.

Assuming third-party libraries, system APIs, or compilers are broken before verifying application logic.

Relying on memory-dependent breakpoints that mask concurrency race conditions during stepping (heisenbugs).

Forgetting to clean up intrusive debug code, verbose logs, or temporary mocks before commit.

Snippets and commands

# Locate the commit introducing a bug with automated git bisect
git bisect start
git bisect bad HEAD
git bisect good v1.4.0
git bisect run npm test

TypeScript

// Inspect stack trace programmatically with structured context
function debugAssert(condition: boolean, message: string, payload?: unknown): void {
  if (!condition) {
    console.error(`[AssertionFailed] ${message}`, {
      payload,
      stack: new Error().stack
    });
    throw new Error(message);
  }
}

Bash

# Inspect real-time network requests and response headers
curl -iv -X POST https://api.service.internal/v1/resource \
  -H "Content-Type: application/json" \
  -d '{"id": 42}'

To review

[ ] Profiling memory leaks using heap snapshots and allocation timelines

[ ] Configuring conditional breakpoints and logpoints in browser and IDE debuggers

[ ] Analyzing core dumps and post-mortem stack traces

[x] Git bisect automation for regression hunting

[x] Structured logging and correlation IDs for request tracing

Jarvis Interviews
Jarvis