Guide

A repro (reproduction) is the smallest set of steps, inputs and environment that makes the bug happen reliably. Without a repro, you are guessing. With a minimized repro, every experiment in the scientific loop is cheap.

Local first when possible: same inputs, same config flags, same data shape. For heisenbugs (races), add logging or run under a thread sanitizer / stress loop rather than hoping it happens in the debugger.

Walking through minimize

  • Cut input size (binary search the payload).
  • Remove unrelated feature flags.
  • Replace the full app boot with a unit test that hits the function.
  • Capture a failing fixture (HTTP recording, DB row) so CI can own it.

Example

API returns 500 for "some" uploads. You binary-search file size and find failures start at 4,194,304 bytes. Hypothesis shifts from "virus scan" to "body size limit." One config check confirms the proxy max body is 4 MB — root cause in minutes, not a day of guessing MIME types.

That is divide-and-conquer on the input axis. The same idea on history is git bisect.

Races refuse to sit still. Run under race detectors and stress harnesses (go test -race, repeated loads). Add sequence numbers to logs to see reordering. Prefer making the bug more likely (smaller sleeps, more threads) over hoping to catch it once. Consider whether the fix belongs in clearer ownership of state (single writer, mutex, transactional integrity) rather than another sleep.

Performance is a bug with a continuous symptom. The same loop applies, but evidence shifts toward profiling: CPU profiles, allocation profiles, query EXPLAIN, lock wait graphs, flame charts. Hypotheses sound like "we are doing N+1 queries on the order line items" or "the regex is backtracking on long subjects." Avoid optimizing without a profile. Gut-driven caching often adds complexity for a millisecond you invented.

A fix without a regression test is a temporary fix. Add the minimized repro as a test.

Trade-off

More logging always vs surgical instrumentation. Permanent high-cardinality logs are expensive and noisy. Prefer temporary targeted logs or dynamic debug levels during the hunt, then keep only what helps operate the system afterward.

Common mistakes

  • Theorizing without a repro. Especially on intermittent bugs.
  • Hoping a race appears in the debugger. Stress it until it is likely.
  • Optimizing "slow" without a profile. You will cache the wrong thing.

Check yourself

  1. Failures start at 4,194,304-byte uploads. What hypothesis did minimizing the input give you?

A body size limit around 4 MB, not MIME type or virus scan.

  1. Why capture a failing fixture?

So CI owns the repro and the bug cannot silently return.

Key takeaways

  • Invest in a reliable, minimized repro before deep theory.
  • Bisect inputs, flags and environment the same way you bisect code.
  • Turn the minimized repro into a regression test.

Further learning

Next: bisecting bugs.