Debugging is the skill that separates good engineers from great ones. Anyone can write code that works. Fixing code that doesn't is harder.
Here's my framework, developed through hundreds of bugs.
Step 1: Read the Error Message
This sounds obvious, but I see engineers skip it constantly. They glance at the error, assume they know what's wrong, and start changing random things.
Read the whole message. The answer is often in the text you didn't read.
TypeError: Cannot read properties of undefined (reading 'map')
at UserList (src/components/UserList.tsx:12:24)
at renderWithHooks (node_modules/react-dom/...)
This tells you:
- What went wrong: Tried to call
.map()on undefined - Where:
UserList.tsx, line 12, column 24 - When: During render (React hooks context)
Don't guess. Read.
Step 2: Reproduce Reliably
Before fixing anything, you need to reliably trigger the bug. "It happens sometimes" isn't good enough.
Find the exact sequence of steps:
- Start from a known state (fresh page load, empty database)
- Do X, then Y, then Z
- Bug appears
If you can't reproduce it, you can't verify your fix works.
Write down the reproduction steps. Future you will forget. Other engineers will need them.
Step 3: Binary Search
Once you can reproduce the bug, find where it starts. Binary search is the fastest way.
For code bugs:
- Comment out half the function
- Still broken? Bug is in the remaining half
- Works now? Bug is in the commented half
- Repeat until you've isolated the line
For git history:
git bisectautomates this- Find a good commit, find a bad commit
- Git binary searches between them
For environment issues:
- Remove half your environment variables
- Remove half your dependencies
- Remove half your config
The goal is to eliminate possibilities quickly.
Step 4: Form a Hypothesis
Once you've isolated the area, form a theory about what's wrong. Be specific.
Bad: "Something's wrong with the API" Good: "The API returns null when the user has no orders, but the frontend expects an empty array"
Write your hypothesis down. This forces clarity and prevents circular debugging.
Step 5: Test Your Hypothesis
Don't just fix it — verify your theory first. Add a console.log or breakpoint to confirm your hypothesis before changing code.
If your hypothesis was wrong, you'll know immediately. If you skip this step and your fix doesn't work, you won't know if your hypothesis was wrong or your fix was wrong.
Step 6: Fix and Verify
Make the smallest change that fixes the bug. Then:
- Verify the bug is gone (run your reproduction steps)
- Verify you didn't break anything else (run tests)
- Add a test that would have caught this bug
The test is important. Bugs cluster — if this part of the code broke once, it'll probably break again.
When to Ask for Help
Time-box your debugging. If you've spent more than 30 minutes without progress:
- Explain the problem to someone (rubber duck debugging works)
- Share what you've tried — this shows respect for others' time
- Be specific about where you're stuck
Good: "I've isolated it to this function. The API returns successfully but the state update never fires. I've verified with logs that the response is correct. Stuck on why setState isn't working."
Bad: "It doesn't work, can you help?"
Common Patterns
"It works on my machine"
Usually environment differences:
- Node version
- Environment variables
- Database state
- Cached files
"It worked yesterday"
Use git bisect or check recent changes:
git log --oneline -20
git diff HEAD~5"It only fails in production"
Usually missing environment variables, different URLs, or timing issues. Add logging, check for hardcoded localhost, compare configs.
"It fails randomly"
Usually race conditions, timing issues, or uninitialized state. Add delays to force the slow path, or add guards for undefined values.
The Meta-Skill
Debugging is pattern matching. The more bugs you fix, the faster you recognize patterns.
Keep a debugging journal. When you fix a tricky bug, write down:
- What the symptoms were
- What the actual cause was
- How you found it
This accelerates pattern recognition. You'll start recognizing bug signatures.
What's your debugging process? I'm always interested in how other engineers approach this.