Debugging
Purpose
Find the actual cause of a defect, not a change that makes the symptom disappear. Debugging is a search problem, and the method is binary search over hypotheses.
When to Use
- A test fails and the reason is not obvious from the assertion.
- Production behavior differs from local behavior.
- An intermittent or timing-dependent failure.
- A performance regression with no obvious cause.
- A bug that "came back" after being fixed.
Capabilities
- Deterministic reproduction, including of flaky failures.
- Bisection over code history, input space, and configuration.
- Instrumentation: strategic logging, breakpoints, tracing, core dumps.
- Concurrency debugging: race detectors, deadlock analysis, lock ordering.
- Performance debugging: CPU and allocation profiles, flame graphs.
Inputs
- The failure: error message, stack trace, failing test, or observed behavior.
- The last known-good state, if any.
- Environment differences between where it fails and where it does not.
Outputs
- A reproduction that fails reliably.
- A stated root cause, with the evidence that proves it.
- A fix, plus a regression test that fails without the fix.
Workflow
- Reproduce — Reduce to the smallest input and shortest path that still fails. If you cannot reproduce it, you cannot verify a fix. For flaky failures, run in a loop until you have a failure rate.
- Read the evidence — Read the entire stack trace, including the parts you have seen before. Read the actual error, not the one you assume it is.
- Form one hypothesis — State it as a falsifiable claim: "the cache returns a stale value because the invalidation runs before the write commits."
- Test the hypothesis — Add instrumentation that would distinguish true from false. Do not change behavior yet.
- Bisect — If no hypothesis survives, bisect.
git bisect over commits; comment out halves of the input; disable half the config.
- Prove the cause — You have the cause when you can turn the bug on and off at will.
- Fix and regress — Write the failing test first, then fix, then confirm the test passes and the rest still do.
Best Practices
- Never change two things at once. You will not know which one mattered.
- "It works now" without an explanation means the bug is still there.
- Trust the machine over your memory of what the code does — read the code that is running, on the branch that is deployed.
- If the bug is in a dependency, prove it with a minimal script before reporting or working around it.
- Timing-dependent bugs are nearly always missing synchronization or an assumed ordering. Look for shared mutable state first.
- Delete instrumentation you added, or promote it to permanent, structured logging. Do not leave debug prints behind.
Examples
Hypothesis log for an intermittent failure:
Symptom : /checkout returns 500 approximately 1 in 40 requests under load.
Evidence: Stack trace shows NullPointerException in CartCache.get(); no error at low load.
H1: Cache eviction races with read.
Test: log cache size + key on every get/put; run 500 concurrent requests.
Result: FALSE — evictions never coincide with the failures.
H2: Cart is written by request thread but read by an async pricing task
before the write commits.
Test: log thread id and transaction id at write and at read.
Result: TRUE — pricing task reads on a different connection, 3-8ms before commit.
Cause: Async task enqueued inside the transaction, executed outside it.
Fix : Enqueue on transaction commit (after-commit hook).
Test : Regression test asserting the task is not enqueued until commit.
Notes
git bisect run <script> automates bisection completely when you have a scripted reproduction. It is the single highest-leverage debugging tool most engineers underuse.
- Heisenbugs that vanish under a debugger are usually timing or optimization-related. Reach for logging and race detectors instead of breakpoints.
- A bug that reappears was never fixed — the original fix addressed a symptom. Reopen the investigation rather than patching again.
1---2name: debugging3description: Use when a bug's cause is unknown. Applies a hypothesis-driven method — reproduce, isolate, instrument, prove — instead of speculative edits, and covers profiler, debugger, and log-based investigation.4---56# Debugging78## Purpose910Find the actual cause of a defect, not a change that makes the symptom disappear. Debugging is a search problem, and the method is binary search over hypotheses.1112## When to Use1314- A test fails and the reason is not obvious from the assertion.15- Production behavior differs from local behavior.16- An intermittent or timing-dependent failure.17- A performance regression with no obvious cause.18- A bug that "came back" after being fixed.1920## Capabilities2122- Deterministic reproduction, including of flaky failures.23- Bisection over code history, input space, and configuration.24- Instrumentation: strategic logging, breakpoints, tracing, core dumps.25- Concurrency debugging: race detectors, deadlock analysis, lock ordering.26- Performance debugging: CPU and allocation profiles, flame graphs.2728## Inputs2930- The failure: error message, stack trace, failing test, or observed behavior.31- The last known-good state, if any.32- Environment differences between where it fails and where it does not.3334## Outputs3536- A reproduction that fails reliably.37- A stated root cause, with the evidence that proves it.38- A fix, plus a regression test that fails without the fix.3940## Workflow41421. **Reproduce** — Reduce to the smallest input and shortest path that still fails. If you cannot reproduce it, you cannot verify a fix. For flaky failures, run in a loop until you have a failure rate.432. **Read the evidence** — Read the entire stack trace, including the parts you have seen before. Read the actual error, not the one you assume it is.443. **Form one hypothesis** — State it as a falsifiable claim: "the cache returns a stale value because the invalidation runs before the write commits."454. **Test the hypothesis** — Add instrumentation that would distinguish true from false. Do not change behavior yet.465. **Bisect** — If no hypothesis survives, bisect. `git bisect` over commits; comment out halves of the input; disable half the config.476. **Prove the cause** — You have the cause when you can turn the bug on and off at will.487. **Fix and regress** — Write the failing test first, then fix, then confirm the test passes and the rest still do.4950## Best Practices5152- Never change two things at once. You will not know which one mattered.53- "It works now" without an explanation means the bug is still there.54- Trust the machine over your memory of what the code does — read the code that is running, on the branch that is deployed.55- If the bug is in a dependency, prove it with a minimal script before reporting or working around it.56- Timing-dependent bugs are nearly always missing synchronization or an assumed ordering. Look for shared mutable state first.57- Delete instrumentation you added, or promote it to permanent, structured logging. Do not leave debug prints behind.5859## Examples6061**Hypothesis log for an intermittent failure:**6263```text64Symptom : /checkout returns 500 approximately 1 in 40 requests under load.65Evidence: Stack trace shows NullPointerException in CartCache.get(); no error at low load.6667H1: Cache eviction races with read.68 Test: log cache size + key on every get/put; run 500 concurrent requests.69 Result: FALSE — evictions never coincide with the failures.7071H2: Cart is written by request thread but read by an async pricing task72 before the write commits.73 Test: log thread id and transaction id at write and at read.74 Result: TRUE — pricing task reads on a different connection, 3-8ms before commit.7576Cause: Async task enqueued inside the transaction, executed outside it.77Fix : Enqueue on transaction commit (after-commit hook).78Test : Regression test asserting the task is not enqueued until commit.79```8081## Notes8283- `git bisect run <script>` automates bisection completely when you have a scripted reproduction. It is the single highest-leverage debugging tool most engineers underuse.84- Heisenbugs that vanish under a debugger are usually timing or optimization-related. Reach for logging and race detectors instead of breakpoints.85- A bug that reappears was never fixed — the original fix addressed a symptom. Reopen the investigation rather than patching again.