Cache invalidation is the discipline of removing or refreshing cached data when the underlying source has changed. Phil Karlton famously claimed it was one of the two hard things in computer science, and he was right. For vibe coded apps, cache invalidation bugs are some of the most painful production failures, the user updates their profile, the cache still serves the old version, and the user thinks the app is broken. This piece walks through the four invalidation strategies that cover almost every case.
The good news is that most cache invalidation problems boil down to picking the right one of four patterns. The bad news is that AI tools rarely pick correctly without prompting.
Why Cache Invalidation Goes Wrong
The default cache pattern AI generates looks like this, store the result of an expensive query under a key, return it on subsequent requests, set a TTL of 60 seconds. This works perfectly until something changes the underlying data within the 60 second window. Now the cache is stale, the user sees outdated information, and the bug is invisible until someone notices and complains.
The deeper issue is that caches are a shadow copy of your data, and any place your data changes is a place that needs to invalidate the shadow. AI does not know which of your endpoints write data, so it cannot know where to wire up invalidation. The result is caches that are correct most of the time, broken in surprising ways some of the time, and nearly impossible to debug because the bug only happens in production under specific timing.
A 2024 production incident analysis from a popular SaaS observability vendor found that cache invalidation bugs accounted for 23% of "data is wrong" complaints from end users, but only 4% of engineering's tracked bugs. The gap exists because cache bugs rarely show up in tests, they show up in user reports.
The pattern to copy is a library card catalog. The catalog is a fast index of where to find every book in the library, but it must be updated every time a book moves, gets added, or gets removed. The librarian's job is to keep the catalog and the shelves in sync. Caches need a similar discipline, every place data changes is a place that updates the catalog.
The Four Invalidation Strategies
Almost every cache invalidation problem can be solved with one of four patterns. The skill is matching the pattern to the use case, not memorizing every possible permutation.
Strategy 1, time based (TTL). The cache expires after a fixed duration regardless of whether the underlying data changed. This is the simplest pattern, the AI default, and the right choice when the data does not change often or staleness up to the TTL is acceptable. Configuration values, lookup tables, and rate-limit counters fit this pattern.
Strategy 2, write through. Every write to the underlying data also writes to the cache. The cache is always in sync because it is updated at the same moment as the source. This works well for high-read, low-write data where the writes are funneled through a single code path. The catch is that any write that bypasses the central path leaves the cache stale.

Strategy 3, event based. A separate event stream broadcasts changes, and any cache subscribing to the relevant events invalidates accordingly. This works well for distributed systems where multiple services hold caches of overlapping data. Pubsub systems like Redis pub/sub or Postgres LISTEN/NOTIFY make this practical for small teams. The catch is operational complexity, you now have a message bus to maintain.
Strategy 4, manual. Someone explicitly tells the cache to invalidate, usually through an admin action, a deploy hook, or a webhook. This sounds primitive but is correct for cases where invalidation is rare and predictable, content management systems, marketing sites, and config rollouts.
Picking the Right Strategy
The honest framework is, pick on the write pattern, not the read pattern. Most engineers think about "what data am I caching" when they should be thinking "where in the code does this data change."
If the data is read-mostly and almost never writes (config, tax rates, country lists), use TTL with a generous duration. If the data has all writes funneled through a single function or service, use write through. If the data is updated by multiple services or out-of-band processes, use event based. If the data changes only when humans push deploys or admin actions, use manual.
Read the rest of the production performance series
Browse the ship categoryThe mistake to avoid is using TTL by default for everything. TTL is the easy choice and the wrong choice for any data where the user expects to see updates immediately. A user who updates their profile should not see the old version for 60 seconds. They will assume the update did not save and try again, which doubles your write load and creates duplicate records.
Making Caches Debuggable
The single most useful technique for cache invalidation is logging when caches hit, miss, and invalidate. Without this telemetry, you are guessing at what your cache is doing in production.
The minimum viable instrumentation is, every cache get logs the key and whether it was a hit or miss, every cache set logs the key and the TTL, every invalidation logs the key and the trigger. Keep these at debug level by default and turn them up when investigating. The pattern that emerges over time is that 80% of your bugs are in 20% of your cache keys, the ones with complex invalidation logic.
The other piece is cache key design. A poorly designed cache key (user_data) cannot be invalidated precisely. A well designed cache key (user:1234:profile:v2) can be invalidated for one user without affecting others, and the version suffix lets you bust the entire cache by deploying a new version. Spend ten minutes designing keys before writing the cache code.

The version suffix in particular is one of the highest-leverage techniques in caching. When you change the shape of a cached object, increment the version, and every old cache entry naturally expires without any manual invalidation. This pattern is what lets you deploy schema changes safely.
The most expensive cache invalidation mistake is the negative cache, storing "this user does not exist" responses. If you cache that response and the user later signs up, your cache will keep saying "does not exist" until the TTL expires. Always invalidate negative caches on the relevant write event.
The corollary is that cache invalidation should happen on every write that could affect a cached value, including deletes, including writes through migrations, including writes through admin actions. Missing any of those is how stale data appears.
What This Means For You
Cache invalidation is genuinely hard, but the four strategies above cover almost every case you will encounter. Start by classifying every cached query by which strategy fits, write the corresponding invalidation code, and add cache hit/miss logging.
- If you're a founder: Most early-stage apps over-cache. The right answer for the first six months is usually no cache at all, just make the database queries fast. Add caching only after you have measured a real problem.
- If you're changing careers: Cache invalidation is one of the topics that separates senior engineers from junior ones, not because it is intellectually hard but because the bug patterns require production scars to recognize. Read every postmortem you can find about cache bugs.
- If you're a student: Build a tiny app with a TTL cache and intentionally trigger a stale read by updating data within the TTL window. Watch the bug. Then implement write-through and watch the bug disappear.
Browse more performance and reliability guides
Read more ship guides