Skip to content

Navid's Blog

Ideas, Experiments, and Lessons Learned

Menu
Menu
Code editor screen glowing in a dark room while debugging a cache bug

I Spent 4 Hours Chasing a Race Condition — It Was My Own Cache Layer

Posted on August 30, 2026 by Navid

Four hours. Two engineers. One bug that only showed up a couple of times a day. And the punchline: the code responsible was mine, written six months ago, with a comment that said “simple wrapper, do not overthink.”

I did not overthink it. That was the whole problem.

The Scene

A Tuesday afternoon, support forwarded us a ticket that nobody wanted to be true: a user was looking at another user’s dashboard. Their name, someone else’s numbers. Someone else’s charts, on their screen, logged in as themselves.

We couldn’t reproduce it, obviously. It happened maybe twice a day across thousands of sessions, and never for us when we tried.

The Spiral

Hour one was logs. Nothing. No errors, no stack traces, just requests happily returning data to the wrong people. Somehow that’s worse than an error.

Hour two we blamed the most recent release and rolled it back. Bug kept happening. Then we blamed WebSockets, because when you have no idea what’s going on, you blame WebSockets. My teammate tore apart the subscription logic for an hour. Nothing. (The WebSockets were innocent, for the record.)

Hour three he added distributed locking around the dashboard query, which felt very responsible, and the bug got more frequent. That’s the moment it clicked. This probably wasn’t a race condition at all. Locking made it worse, which meant whatever we had liked concurrency.

The Discovery

Hour four I finally opened our cache helper. Mine. The one with the confident comment. And there it was:

When the user ID was empty, the cache key fell back to the string "shared". For every user. Every request.

A user with a glitchy session token would hit the endpoint with an empty ID, prime the cache under "shared", and everyone after them read that same entry until it expired. Fresh dashboards, served fast, to whoever came next.

The fix was one if statement. Four hours.

What I Learned

Here’s the thing I keep thinking about: intermittent bugs are usually data bugs that look like concurrency problems. If locking makes a bug worse, stop locking and start asking what data is being shared.

  • A cache key is a contract. “Fall back to something” is not a key strategy, it’s a data leak with extra steps.
  • Test with two users. Every integration test we had used one user, because one user is easy and two users is reality.
  • Read your own old code with suspicion. Six-months-ago me was tired too, and he also wanted to go home.

What I’d Do Different

  1. Fail loud on a missing cache key. Never guess one.
  2. Add a test with two concurrent users to the dashboard suite.
  3. Log cache key misses so the next version of me finds this in minutes, not hours

So yeah. Four hours for one if statement. It happens. The cache layer is still fast, by the way. Just slightly less clever now.

Categories

  • AI Experiments
  • Coding
  • Debugging Stories
  • Hot Takes
  • Ideas
  • Lessons Learned
  • Productivity
  • Project Management
  • Systems & Architecture
  • Uncategorized
  • Vibe Coding

Recent Posts

  • 5 ORM Mistakes That Pushed Me Back to Raw SQL
  • Norway Vanished From Our App for Two Days — It Was One Unquoted Line
  • I Ignored a Deprecation Warning for 11 Months — It Worked Until It Didn’t
  • Why I Stopped Time-Blocking (And What I Do Instead)
  • I Vibe-Coded a Tool in One Afternoon — It Died on Its First Real User