The Bug That Took Me Three Days to Find

The Bug That Took Me Three Days to Find
The fix was one character.
After three days. Three days of staring, swearing, doubting my sanity, and at one point seriously wondering if my computer was haunted. The bug that humbled me most in my career came down to a single character I'd been looking at the whole time.
Here's the full embarrassing story, and the debugging lessons I extracted from the wreckage — the ones I wish I'd had before those three days.
Quick Answer
The bug took three days because I assumed I knew where it was and never questioned that assumption. The real lesson of long debugging sessions is almost never "the bug was tricky" — it's "I trusted something I shouldn't have." You find these bugs faster by questioning your assumptions, reproducing reliably, bisecting the problem, and reading what the code actually does instead of what you meant it to do.
Day one: certainty, the enemy
The symptom was maddeningly intermittent. Sometimes a user's data would come back subtly wrong — an old value where a new one should be. Not always. Maybe one in twenty times. The kind of bug that makes you doubt the bug report.
I was certain it was a caching issue. Certain. We had a cache layer, the symptom looked like stale data, case closed. I spent all of day one in the caching code, adding logging, tweaking expiry, convinced the answer was in there.
It was not in there. The cache was innocent. But my certainty meant I didn't even look anywhere else for a full day. I'd decided the answer, and then I only searched where I'd decided.
This is the first and biggest lesson: the assumption you're most confident about is the one hiding the bug. Confidence feels like progress and is often the exact thing keeping you stuck. Distrusting your own certainty is a recurring theme in the debugging method that changed how I work.
Photo by Lee Campbell on Pexels
Day two: I couldn't reproduce it, so I couldn't fix it
Day two I admitted the cache was clean and faced the real problem: I couldn't make the bug happen on command. One in twenty, randomly, in production-like conditions only.
You cannot fix what you cannot reproduce. So day two became entirely about reproduction, and that was the most useful pivot I made.
I stopped trying to fix and started trying to trigger. I wrote a script to hammer the code path hundreds of times and log everything. I controlled the inputs. I removed randomness one source at a time. After hours of this, I finally had it: a reliable repro that failed every single time under one specific condition.
That was the turning point. The moment I could summon the bug at will, the rest was just narrowing.
An intermittent bug you can't reproduce is a ghost. A bug you can reproduce on demand is already half-solved.
Here's the reproduction discipline I'd skipped on day one and swear by now:
- Find the smallest input that triggers it.
- Remove every source of randomness you can.
- Make it fail reliably before you try to fix anything.
- Only then start changing things.
Day three: bisecting toward the truth
With a reliable repro, I started bisecting — the single most powerful debugging move there is.
I didn't try to understand the whole flow. I just kept cutting the problem in half. Is the bad value already wrong when it enters this function? Yes. So it's upstream. Is it wrong when it leaves the layer before that? Yes. Keep going up. Is it wrong right after we read it from the source? …No. So the corruption happens between the read and the place I'd been staring at for two days.
Binary search through a call stack. Each check eliminated half the remaining code. I went from "somewhere in this 5,000-line subsystem" to "in these four lines" in about an hour, after two days of unfocused staring. The whole episode is a small case study in the brutal truth about becoming a senior developer — seniority is mostly process discipline, not raw cleverness. It's worth noting that the MDN Web Docs debugging guidance leads with exactly this: reproduce reliably and narrow systematically before you theorize.
And there it was. A variable assignment that used = where the logic needed a comparison, inside a condition that only mattered one in twenty times. One character. Sitting in plain sight in code I'd read a dozen times, because I'd read what I meant it to say, not what it said.
// what I read every time (what I meant):
if (user.role === "admin") { ... }
// what was actually there (the bug):
if (user.role = "admin") { ... }
My brain auto-corrected it every single time I looked. That's the cruelest part of these bugs: you don't see the character, you see your intention.
Why one character cost three days
The fix took one second. The finding took three days. And every hour of that was self-inflicted by a process mistake, not by the bug's difficulty.
| What I did | What I should have done |
|---|---|
| Assumed it was the cache | Questioned my assumption on hour one |
| Tried to fix before reproducing | Built a reliable repro first |
| Read the code for what I meant | Read the code for what it says |
| Stared at the whole subsystem | Bisected to halve the search space |
| Debugged alone, stubbornly | Asked a fresh pair of eyes day one |
Every long debugging session I've had since traces back to one of these. The bug is rarely as hard as my process is bad.
Photo by Oluwaseun Duncan on Pexels
The debugging checklist I use now
After that bug, I wrote myself a literal checklist, and I run it the moment a bug resists for more than an hour. It has saved me countless repeats of those three days.
- What am I assuming? Write down the thing I'm sure of. That's the first suspect, not the last.
- Can I reproduce it reliably? If no, stop everything and make it reproducible before touching anything else.
- What changed? Bisect the history, not just the code. A version-control bisect finds the breaking commit faster than reading ever will.
- Am I reading what's there or what I meant? Read the suspicious code out loud, character by character. Or have a teammate or AI assistant read it cold, with no idea what it's "supposed" to do.
- Have I asked anyone? A fresh pair of eyes that doesn't share my assumptions is worth more than another hour alone. Pride is the most expensive debugging tool there is.
That last one stings the most. I could have ended the three days on day one by walking over to a colleague and saying "this makes no sense, can you look?" Their brain wouldn't have auto-corrected my character, because they didn't know what I meant it to say.
There's one more habit I picked up from that week: I now narrate my assumptions out loud, even alone. "I'm assuming the value is correct when it enters here. Have I actually checked that, or do I just believe it?" Spoken aloud, an unchecked belief sounds exactly as flimsy as it is. Half my long bugs have died the moment I said the magic words — "wait, have I actually verified that?" — and realized I hadn't. The bug wasn't hiding. My confidence was hiding it.
The bottom line
I lost three days to a one-character bug, and not one of those days was the bug's fault. They were my assumptions, my pride, and my refusal to reproduce before fixing.
The hard part of debugging is almost never the bug. It's noticing the thing you were certain about was wrong.
I'm a calmer, faster debugger now — not because I got smarter, but because I learned to distrust my own certainty and to make the bug reproducible before I touch it. The character was always in plain sight. The lesson was learning how to actually see it.
If this saves you even one three-day rabbit hole, run the checklist next time a bug resists for an hour — and the senior-developer pillar has more hard-won process notes where this came from.
Next time a bug eats your afternoon, try the cruelest question first: what am I sure about that I haven't actually checked?
Key Takeaways
- Question your most confident assumptions first—they’re the most likely to hide the bug, not the least.
- Stop trying to fix the bug until you can reproduce it reliably; script the failure path and remove randomness before touching anything else.
- Bisect the problem systematically: eliminate half the remaining code with each check to narrow from 'somewhere in 5,000 lines' to 'these 4 lines' in under an hour.
- Read the code character-by-character out loud or have someone else read it cold—your brain auto-corrects typos to match intent, hiding the actual bug.
- Bring in a fresh pair of eyes (or an AI assistant) early; they won’t share your assumptions and will see what you’ve been missing.
- Narrate your assumptions aloud: saying 'I assume X is true—have I actually verified it?' exposes unchecked beliefs that are likely wrong.
Frequently Asked Questions
How do I know when to ask for help instead of pushing on?
A rough rule: if you've made zero real progress in an hour, your mental model is probably wrong, and another hour alone won't fix a wrong model. That's the moment to bring in someone whose assumptions differ from yours.
What's the single most useful debugging skill?
Reliable reproduction. Everything downstream — bisecting, logging, fixing — depends on being able to trigger the bug at will. Time spent making a bug reproducible is never wasted; it's the foundation for all the rest.
How do I avoid my brain "auto-correcting" code I'm reading?
Change the context. Read it out loud, read it in a different font or tool, or have someone else read it cold. Your eyes lie about code you wrote because they show you your intent. A fresh reader sees the actual characters.
Are debuggers better than print statements?
Both have their place. Debuggers shine for inspecting state at a breakpoint; logging shines for intermittent and timing bugs you can't pause. For my three-day bug, scripted logging that ran the path hundreds of times was what finally pinned it.
Can AI tools actually help find bugs like this?
Sometimes, yes — pasting suspect code into an AI assistant with no context can catch exactly the kind of typo your own eyes skip, precisely because it has no "intent" to project onto the code. It's a cheap fresh pair of eyes. Use it early.




Comments
Sign in to join the conversation
No comments yet. Be the first to share your thoughts!