Tutorials / Tips & tricks / Debug systematically
📝 Written ● Intermediate Updated 2026-05-19

Debug systematically with LingCode

Most "debugging" is pattern-matching against guesses, then trying the guesses. Systematic debugging is slower per step and faster overall: reproduce, isolate, hypothesize, test, narrow — and only then propose a fix. LingCode follows the method when you give it the method.

Why guess-and-patch is the slow path

0

The fast-feeling debugging loop is: read the stack trace, guess a cause from the shape, try a fix, see if the error goes away. It feels fast because each iteration is short. It's slow because:

  • The first guess is almost never the root cause — it's the most-recently-seen similar pattern, which is correlation, not causation.
  • You can't tell if a fix worked if you didn't isolate the bug first. The error stops happening for any reason and you call it solved, until it returns at 2am.
  • "Stops crashing" is not the same as "bug fixed." A try/catch around the symptom looks green and masks the underlying state corruption.

Systematic debugging trades the dopamine of fast iteration for the certainty of "I know what went wrong and I know my fix addresses that specifically." The skill in this tutorial gives LingCode an explicit five-step protocol so it doesn't fall into pattern-match mode on your behalf.

What you need

1
  • LingCodedownload the installer.
  • A bug — a test failure, a crash, a wrong output, or "this used to work."
  • The ability to run the code that fails locally. Bugs you can't reproduce are a different — harder — problem.

Step 1 — Reproduce, before reading any code

2

The first thing LingCode should do is reproduce the bug locally and capture the exact command, input, and observed output. Not read the source. Not theorize. Just reproduce:

Before reading any source, reproduce the bug. Report:
- Exact command run.
- Exact input (paste it; don't paraphrase).
- Exact output / error / stack trace (paste verbatim).
- Whether it's deterministic (run it 3 times — same result?).

If you can't reproduce, stop. Ask for: the minimal failing
input, environment differences, or a recording. Don't guess
at causes for bugs you haven't seen.

If LingCode can't reproduce the bug, every subsequent step is theater. The honest answer is "I need a repro" — not a fix attempt.

Deterministic vs. flaky matters. If the bug fires 1 in 5 times, narrow the variable that controls flakiness (timing, ordering, randomness) before continuing. Intermittent bugs solved by "I changed something and didn't see it again" come back.

Step 2 — Isolate the smallest failing case

3

Most bug reports come wrapped in a full app, a long test suite, or a complex input. LingCode's next job is to peel layers off until the bug fires from the smallest possible test:

Reduce the failing case. Aim for:
- A single failing test (or a 10-line script) that triggers
  the bug.
- The smallest input that still reproduces.
- The narrowest call path — strip middleware, unrelated
  state, optional config.

Each reduction must still reproduce. If a reduction stops
reproducing, you've found something — either the bug is in
the part you removed, or your reduction was wrong. Note it.

"Each reduction must still reproduce" is the rule. Removing code until the bug disappears and not investigating why is how you fix the wrong thing.

Step 3 — Hypothesize, with a falsifiable prediction

4

Now — and only now — LingCode reads code and forms a theory. The theory must come with a prediction that can be checked:

Form a single hypothesis. It must:
- Name the specific function / state / interaction you think
  is wrong.
- Predict what will happen if your hypothesis is correct
  AND a tweak is applied that should change behavior.
- Predict what will happen if your hypothesis is wrong.

"I think it's a race condition" is not a hypothesis. "I think
read_config() returns before write_config() completes on cold
start, so adding a 100ms sleep before read should make the
bug rarer but not gone" is a hypothesis.

Two hypotheses simultaneously is fine. Three is a sign you're not actually reading the code — you're brainstorming. Narrow before testing.

Step 4 — Test the hypothesis, not the fix

5

This is the step that separates systematic debugging from guess-and-patch. The test is designed to prove the hypothesis right or wrong — not to make the bug go away:

Test the hypothesis. Pick the cheapest probe that distinguishes
right-from-wrong:
- A print / log at the suspected boundary, showing the value
  you predicted.
- A breakpoint or debugger inspection.
- A unit test that exercises the suspected interaction.
- A diff of the value before and after the suspected mutation.

If the probe contradicts the hypothesis, the hypothesis is
wrong. Go back to step 3. Do not "adjust" the hypothesis to
fit — write a new one.
"The error went away" is not confirmation. Errors disappear for many reasons unrelated to your theory — caching, timing, side effects in your test setup. The hypothesis is confirmed only when the probe shows what you predicted.

Step 5 — Narrow to the minimal fix

6

Once the hypothesis is confirmed, LingCode writes the smallest change that addresses the actual cause — not the symptom:

Propose the fix. It should:
- Address the cause identified in step 4, not the symptom in
  step 1.
- Be the smallest change that achieves that.
- Come with a regression test that fails before the fix and
  passes after. (No regression test = no proof the fix works.)

Then re-run the original reproduction from step 2.
If it still fires, the fix is incomplete — go back to step 3.
If it doesn't, run the full test suite to check for collateral
damage. Document one-line cause + one-line fix in the commit
message.

The regression test is the artifact that proves you understood the bug. If you can't write a test that catches it, you didn't understand it — you just made it stop happening.

When to break the protocol

7

Systematic debugging is the right default. It's overkill for two cases:

  • Trivial typos and obvious errors. "Cannot read property 'name' of undefined" on line 42 where line 41 doesn't await an async call — fix it. Don't ceremoniously hypothesize.
  • Production fires. If users are down, the right move is the fastest known mitigation (rollback, feature flag off, restart). Run systematic debugging afterward, on the post-mortem, with the system stable.

For everything in between — flaky tests, "works for me" reports, intermittent crashes, mysterious slowdowns — the protocol pays for itself. Have LingCode ask "is this a fire or an investigation?" up front:

Before debugging, classify:
- FIRE: users down, ship a mitigation now, investigate after.
- INVESTIGATION: bug is contained, follow the 5 steps.
- TYPO: obvious error visible in the diff, just fix it.

Don't run a full systematic-debug ritual on a typo.
Don't skip steps on an investigation because it "looks easy."

Use this in LingCode

8

The full debugging protocol is packaged as a skill — drop it into your LingCode skills folder and LingCode will reach for it on any test failure or crash:

---
name: systematic-debugging
description: Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes. Triggers: 'this is broken', 'why does X fail', 'debug this', 'find the bug', stack trace pasted, failing test output, runtime exception, 'doesn't work', 'unexpected behavior'. Actions: five-step protocol — reproduce → isolate → hypothesize → test the hypothesis (not the fix) → narrow to minimal fix with a regression test. Triage gate: FIRE (drop everything) / INVESTIGATION (full protocol) / TYPO (one-line fix, skip protocol). Anti-pattern: pattern-match-and-guess, fix-before-reproducing.
---

Debug systematically, not by pattern-matching. Most "fixes"
that make errors disappear without proving cause come back.

Classify first: FIRE (mitigate now), INVESTIGATION (follow
the protocol), or TYPO (just fix it).

For INVESTIGATIONS:

1. REPRODUCE. Before reading any source. Capture exact
   command, input, output. Confirm determinism. If you
   can't reproduce, stop and ask — don't guess.

2. ISOLATE. Reduce to the smallest failing case. Each
   reduction must still reproduce. If a reduction stops
   reproducing, investigate why before continuing.

3. HYPOTHESIZE. One specific theory naming function /
   state / interaction. Include a falsifiable prediction:
   what should happen if right, what if wrong.

4. TEST the hypothesis, not the fix. Use the cheapest
   probe that distinguishes right from wrong (log, debugger,
   diff). "The error went away" is not confirmation —
   the probe must show what you predicted.

5. NARROW to a minimal fix that addresses the cause, not
   the symptom. Write a regression test that fails before
   and passes after. Re-run original repro. If it still
   fires, the fix is incomplete.

Document cause + fix in one line each in the commit.

Save as ~/.lingcode/skills/systematic-debugging/SKILL.md — see Install a skill for the exact location and how skills get discovered.

Get LingCode →

What's next