The Cache That Outlived Its Assumptions
I have a small API route that answers one question: what was a given day like, in one sentence each for what I was coding, what I was listening to, and what I wrote. It reads a log of Spotify plays off disk, figures out the dayâs top artist, and returns a short phrase. Nothing exotic.
I built it by copying the shape of an older route that does almost the same thing for a personal dashboard â read the play log, cache it in memory so Iâm not hitting the filesystem on every request, serve it back. Sensible, boring, correct. I shipped it, moved on, and a few days later got asked why todayâs data wasnât showing up even though the underlying log clearly had it.
The pattern was right. The context wasnât.
The older route the caching came from only ever answers questions about days that have already fully happened â itâs a personal history browser, and history, by definition, isnât still being written. Caching the play log forever is completely safe there: an already-elapsed dayâs data doesnât change after the fact, so thereâs nothing for the cache to go stale against.
My new route gets asked about today constantly, and todayâs play log is being appended to in real time by a background poller. The moment I copied the caching code, I also copied an assumption â âthis data doesnât change once readâ â that was true in the route it came from and false in the route I was writing it into. Nothing in the code said so. It was true by context, not by anything visible on the page.
Assumptions donât travel with a comment attached
This is the part that actually bothers me: the bug wasnât a logic error. Every line did exactly what it looked like it did. Cache the array, return it, refresh never. The mistake was entirely in the gap between two routesâ unstated preconditions, and copy-pasting code copies the code perfectly while copying the reasoning behind it not at all, unless you go out of your way to write that reasoning down somewhere itâll actually get read again.
I found it the boring way â comparing two endpoints reading the same underlying file, noticing one had todayâs plays and the other didnât, and working backward. Once I saw it, it was a two-line fix and an obvious one in hindsight. Obvious in hindsight is exactly the problem with this whole category of bug: itâs never obvious in foresight, because the code youâre copying from is, by definition, already proof that the pattern works. It worked. It just worked for reasons that didnât apply to where I put it next.