The one idea
Debugging is narrowing, not fixing.
You are not looking for the solution. You are cutting the space of possible causes in half, then in half again, until only one thing is left. The fix is usually obvious once you know where the problem is. Everyone skips to the fix, which is why everyone gets stuck.
Make it fail on demand, read what it says, and cut the problem in half until there is nowhere left for it to hide.
Reproduce reliably, read the actual error, form one hypothesis, test it, change one thing. Write down what you have ruled out.
Binary search the failure. Establish a deterministic repro, bisect the code path or the commit history, and verify the state at the midpoint with logging or a breakpoint before assuming.
The method
| Step | Question it answers |
|---|---|
| 1. Reproduce | Can I make it fail whenever I want? |
| 2. Read the error | What is the program actually telling me? |
| 3. Narrow | Where between input and output does it stop being right? |
| 4. Inspect | What is the real value here, not the value I assume? |
| 5. One change | Did that specific thing fix it? |
| 6. Confirm | Does it now pass the repro from step 1? |
Skipping step 1 makes every later step meaningless, because you cannot tell whether your change fixed anything or the bug simply did not appear this time.
Reproduce it first
A bug you cannot reproduce cannot be fixed, only accidentally disturbed.
Write down the exact steps: this input, this browser, this account, this order of clicks. Then do it again and confirm it fails again. Then try to make it fail with less — fewer steps, smaller input, simpler data.
That shrinking is the most valuable thing you can do. "It fails when a CSV has a blank final line" is a fixable bug. "It sometimes fails on import" is a mood.
A report generator "randomly" fails. Nobody can pin it down. Someone finally notices it fails for exactly the clients whose name contains an apostrophe.
Nothing was random. The repro was hiding in a field nobody thought to look at. Once it was "fails when the name has an apostrophe", the fix took ten minutes.
Read the actual error
Most people see a red block of text and scroll past it. That text is the most specific information you will ever get about the problem, and it is free.
In a stack trace — the list of function calls printed when a program crashes — read it like this:
- The last line is usually the type of problem and a short description. This is the what.
- The lines above it are the path the program took to get there, most recent at the bottom in Python, at the top in JavaScript.
- Look for your own filenames. The trace usually passes through library code you did not write. The last line mentioning a file you wrote is where to start.
Then paste the error into a search engine or an AI — but paste the exact message,
with your specific filenames and values removed. "KeyError: 'total'" finds
answers. "my python thing broke" does not. That is the same targeting skill as
search-like-you-mean-it.
Try this
Here is a Python traceback. In one sentence: what is wrong, and where do you look first?
Traceback (most recent call last):
File "report.py", line 47, in <module>
total = row["amount"] + total
KeyError: 'amount'
Narrow it down
Once you know roughly where, cut the distance in half.
Halving the search space
1 of 5Confirm the input is what you think it is.
Before suspecting your logic, print the raw input at the very start. An astonishing share of bugs are "the file had a header row", "the number is a string", "there is an invisible space".
You are not testing your code yet. You are testing your assumption about reality.
Why "it works on my machine"
The program is not the only thing that runs. Everything around it runs too, and that surrounding context differs between your laptop and anywhere else.
| Difference | How it shows up |
|---|---|
| Different versions of the language or a library | Works locally, crashes in exactly one function |
| Environment variables set on your machine only | "Missing configuration" or silent wrong behaviour |
| File paths that exist only on your machine | "No such file or directory" |
| Case-sensitive filenames | Windows finds Header.png, Linux does not |
| Your machine already logged in | Works for you, "unauthorised" for everyone |
| Real data is bigger and messier than test data | Fine with 10 rows, times out with 100,000 |
The productive question is never "why does it fail there?" It is "what is different there?" That question has a finite, listable answer. The first one does not.
Your challenge
Level 3 · IndependentTake a program you have — yours or one an AI wrote — and deliberately break it in a way you then forget the details of. Change a variable name in one place. Come back to it the next day.
Fix it using only the method: reproduce, read the error, narrow with prints, one change, confirm. Time yourself.
You have succeeded when you can say which single line was wrong and how you proved it — not "I fiddled with it and it works now".
What people usually get wrong
- Changing several things at once. You lose the ability to attribute the result to anything. This is the number one time-waster.
- Not reading the error. It names the file and the line. People skip past it to search a vague description of the symptom instead.
- Assuming instead of printing. "The list must be empty by now" is a hypothesis. One print turns it into a fact.
- Fixing the symptom. Wrapping the crash in a catch-all that ignores errors makes the message go away, not the bug. Now it fails silently, which is worse.
- Debugging while tired. After the second hour your hit rate collapses. Twenty minutes away genuinely outperforms another hour.
- Not asking. If someone nearby has seen this before, they can end it in thirty seconds. Bring your repro and your ruled-out list, not "it's broken".
How someone experienced does it
Experienced developers ask "when did it last work?" before anything else. If it
worked yesterday, the cause is in what changed since yesterday, and version
history turns a hundred possible causes into a list of ten commits. That is why
git-and-not-losing-your-work is a debugging tool and not just a backup tool.
They also distrust the word "sometimes". Intermittent bugs are almost never random. They are timing, ordering, caching, one bad row, or a specific user's data. "Sometimes" means you have not found the variable yet.
And the strongest habit: they explain the problem out loud, to a person or to nobody. Saying "it reads the file, then it parses it, then — oh" is a real and well-known phenomenon. Forcing yourself to state each step in order exposes the step you had been assuming rather than checking.
When not to use this
Stop debugging and step back when:
- You have been on it more than about an hour with no narrowing. You are probably solving the wrong problem. Re-read what the error actually said.
- The fix is getting more complicated than the feature. Sometimes the design is wrong and three lines of a different approach beats fifty lines of patch.
- It only fails for one user. That is often data or permissions, not code. Look at their account and their input before reading another line.
Prove it
Keep a short bug log for two weeks. One line per bug: the symptom, the actual cause, and how long it took.
After ten entries you will see your own pattern — most people find that the majority of their bugs are the same two or three kinds of mistake. That list is worth more than any general debugging advice, because it is yours.
Keep learning this
Paste this into any AI assistant. It turns the assistant into a tutor that tests you instead of just answering you.
Act as an experienced practitioner who is good at teaching. I have just learned debugging systematically and reading error messages. Assume I am intelligent but relatively new to this — treat me as intermediate level. Work through this in order, and wait for my reply at each step: 1. Ask me 5 questions that test whether I actually understood debugging systematically and reading error messages. Do not reveal the answers yet. 2. After I answer, tell me which parts I got right, which I got wrong, and which I only half-understand. Explain only what I misunderstood — do not re-teach what I already know. 3. Give me one practical challenge based on something I could genuinely encounter at work or in daily life. Do not solve it for me. 4. Evaluate my solution the way an experienced person would judge it, including what a professional would have done differently. 5. Tell me what to learn next, and why that comes next. 6. Give me trustworthy sources for deeper study — prefer official documentation, primary research or standards bodies over blogs and videos. Rules for you: no buzzwords. No motivational filler. Say "I'm not certain" when you are not certain, and tell me which parts of your answer I should verify myself. Clearly separate facts from your recommendations and your opinions.
Become independent at this
Use this when you want a path from where you are to actually good, with checkpoints you can test yourself against.
I want to become independently capable at finding out why something is broken — not permanently dependent on AI, tutorials or step-by-step guides. Design a progression for me with five stages: Beginner, Guided practice, Independent practice, Real-world application, Professional level. For each stage tell me: - what I must know - what I must be able to do without help - the mistakes people make at this stage - one practical challenge - one real project that would prove I reached this stage - one way I can test myself honestly Then tell me the signals that I am ready to move to the next stage, and the signals that I have skipped ahead too early. Keep the theory to the minimum I actually need. Focus on ability I can transfer to situations you and I have not discussed.