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.
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.
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 Ilya Pavlov on Unsplash
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:
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.
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 John Schnobrich on Unsplash
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.
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.
Q: 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.
Q: 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.
Q: 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.
Q: 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.
Q: 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.
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?
No following, no network, no luck. Just an unglamorous system I ran for eighteen months. Here's exactly what I did.

I went from 200 to 11,000 subscribers without hiring anyone. AI didn't write my newsletter — it did everything around it.

I chased big, audacious goals for years and burned out every time. Then I built my whole life around wins so small they felt like cheating.

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