Skip to main content
← All posts

The Backend Bug That Took Me Three Days (And What I Actually Missed)

A walkthrough of a specific debugging story — not the fix, but the wrong assumptions that kept me stuck for three days.

The bug itself was simple. A MongoDB query was returning stale data in specific circumstances — not always, not never, just sometimes. The kind of intermittent failure that makes you question your understanding of how databases work at a fundamental level.

The fix, when I finally found it, was four lines of code. The three days before that were a different story, and that’s what I want to write about — because the wrong assumptions were more interesting than the answer.

The setup

I was building an API endpoint that handled sequential writes and reads: a user action would trigger a write, and a subsequent read was supposed to reflect that write immediately. In testing, it mostly worked. In slightly heavier usage, it would occasionally return the data from before the write.

Not every time. Maybe 15% of requests under concurrent load. Enough to be clearly a bug. Not enough to reproduce reliably on first glance.

Assumption 1: The bug was in the query

My first instinct was the MongoDB query itself. I spent most of day one rewriting it different ways — adjusting projection fields, switching between findOne and find().limit(1), checking indexes, reading the docs looking for some documented edge case I’d missed.

The query was fine. It was doing exactly what I told it to do. I was looking in the wrong place entirely.

This is a pattern I’ve noticed in my own debugging: I start with whatever I touched most recently or feel least confident about. Both of those are biases, not diagnosis. The bug doesn’t care where I lack confidence.

Assumption 2: If it fails, it fails consistently

This one cost the most time. I kept writing tests expecting reproducible results, and when the failure didn’t show up, I’d convince myself the issue was resolved — then it would reappear under slightly different conditions.

What I wasn’t accounting for: the bug was timing-dependent. It only surfaced when two operations happened in close enough succession that one hadn’t fully propagated before the other read.

The lesson isn’t “always think about timing” in some vague way. The more precise version: when a bug is intermittent, the first question should be “what’s racing?” — not “what’s wrong with the logic?”

Intermittent bugs are almost always caused by one of three things: timing, shared state, or environmental differences between runs. I should have asked that question on day one instead of day two.

Assumption 3: The stack trace was pointing me to the problem

The error surfaced in a specific file, on a specific line. That line was not where the problem was. The problem was three function calls upstream, in code I’d written two weeks earlier and considered stable.

I was treating the stack trace as a location — this is where the problem is — when it was actually a symptom trace — this is where the problem became visible.

Knowing the difference matters. A stack trace tells you where execution failed, not where the failure originated. For race conditions especially, those two things are often far apart.

What actually fixed it

A connection pooling configuration issue. Under concurrent load, requests were occasionally reading from a connection that hadn’t yet received the acknowledged write from another connection in the pool. Once I understood the actual execution flow — not the flow I’d assumed based on how I’d written the code — the fix was straightforward.

Four lines. Three days of eliminating wrong beliefs about where the problem could be.

The method I use now

Before I start debugging anything non-trivial, I write down my assumptions explicitly:

  • I believe the data is being written successfully before the read fires
  • I believe these two operations share the same database connection
  • I believe the query is returning the most recent document

Then I test each assumption directly, starting with the ones I’m most confident about — because those are the ones I’m least likely to question otherwise.

It sounds slow. In practice it’s faster than three days of looking in the wrong direction.

Debugging is mostly just removing wrong beliefs as efficiently as possible. The skill isn’t knowing the answer — it’s identifying which assumption to test next.