Skip to main content
Building with AI (Vibe Coding)

⏱ About 20 min20 XP

Systematic Debugging

When code does not behave as expected, the worst thing you can do is start changing things randomly until something works. That approach — sometimes called 'shotgun debugging' — occasionally stumbles onto a fix, but it produces no understanding, often introduces new problems, and is completely unreliable for anything non-trivial. Systematic debugging is the alternative: a disciplined method that treats a bug as a hypothesis-testing problem, not a guessing game.

The Scientific Method, Applied to Code

A defect is a discrepancy between what you expect the code to do and what it actually does. The systematic approach applies the same structure as a scientific experiment: 1. Reproduce: confirm you can make the bug happen consistently. A bug you cannot reproduce cannot be fixed reliably. Identify the exact input, environment, and steps that trigger it. 2. Isolate: narrow down where the discrepancy occurs. Is the problem in the function you called, or the function that called it? Binary search works well here — divide the code in half, check whether the bug is in the first half or second half, repeat. 3. Hypothesize: form a specific, testable prediction about the cause. Not 'something is wrong with the database code' but 'the query is returning null because the user ID is being passed as a string instead of an integer.' 4. Experiment: make a targeted change designed to test the hypothesis. Do not change multiple things at once — that makes it impossible to know which change mattered. 5. Conclude: did the experiment confirm or refute your hypothesis? If confirmed, fix the root cause properly (not just the symptom). If refuted, update your hypothesis and repeat. This cycle is not slow — for simple bugs it takes 30 seconds. Its value is that it remains reliable even for bugs that take days to resolve.

Root Cause vs. Symptom

A symptom is the observable effect: 'the button does nothing when clicked.' A root cause is the actual defect: 'the event listener is attached to a DOM element that does not exist when the page loads because the script runs before the DOM is ready.' Fixing only the symptom — adding a null check around the button — leaves the underlying problem intact and creates technical debt. Always fix the root cause.

Practical techniques for the isolation step: Print/log statements: insert output at strategic points to observe what the program's state actually is at each stage. The discrepancy between expected and actual state points to the location of the bug. Remove them after fixing. A debugger: most environments have an interactive debugger that lets you pause execution at a specific line (a 'breakpoint'), inspect the values of all variables, and step through code one statement at a time. This is more powerful than print statements for complex control flow. Bisecting: if a large block of code might be the source, comment out half of it and test. If the bug disappears, it was in the removed half. If it persists, it is in the remaining half. Repeat until you find the exact line or block. Rubber duck debugging: explain your code and the bug out loud, step by step, to an inanimate object (or a patient classmate). The act of articulating your assumptions precisely often reveals the one you got wrong.

Debugging AI-Generated Code

AI-generated code has predictable failure modes that shape where you look during debugging: Incorrect assumptions about data shape: the AI may assume a function always receives a list when sometimes it receives a single item, or assume a dictionary always has a key that is sometimes absent. Missing edge-case handling: the AI solves the central case and ignores boundaries — empty collections, zero values, negative numbers, None/null. Context errors: the AI's suggestion fits a common pattern but not your specific architecture. A function may work correctly in isolation but break because it assumes a database schema or API contract that does not match yours. When debugging AI-generated code, always ask: what did the AI assume about the input or environment that might not be true in my specific case?

Prompt Challenge

Write a prompt that asks an AI to help you debug a specific function without having the AI rewrite the code for you.

Your prompt should…

  • Describe the exact symptom and the input that triggers it
  • Ask for a hypothesis about the cause, not a rewritten solution
  • Request a single targeted test or change to verify the hypothesis
Do Not Debug by Rewrote

Asking an AI to 'fix this bug' often results in a rewritten version of the function that avoids the symptom without understanding the root cause. You end up with new code you do not understand and no knowledge of what was wrong. Ask for a hypothesis; test it yourself; then fix.

You are debugging a function that sometimes returns None unexpectedly. You add a print statement before the return statement and confirm the value is not None there. What does this tell you?

Which debugging strategy most directly applies the principle of binary search?

Bug Hunt

  1. Step 1: Ask an AI to generate a short function with a deliberate bug hidden in it — tell it to introduce a bug but not tell you what it is. Alternatively, use a bug a classmate has introduced.
  2. Step 2: Apply the systematic debugging method. Write down each step:
  3. a) Reproduce: what exact input triggers the wrong behavior?
  4. b) Isolate: where in the function does the state first diverge from expected?
  5. c) Hypothesize: write a one-sentence specific cause hypothesis.
  6. d) Experiment: what single change tests your hypothesis?
  7. e) Conclude: was your hypothesis correct? What was the actual root cause?
  8. Step 3: Fix the root cause.
  9. Step 4: Write one test that would have caught this bug before you ran the code.
  10. Reflect: how many steps did you need? Would random changes have found this faster or slower?