Imagine a grocery store that never checks expiration dates. Milk from three months ago sits right next to the fresh batch. Nobody can tell the difference from the outside. A customer grabs the old one, takes a sip, and, well, you get the picture.
That's exactly what happens when you cache data without a . The cache happily serves a user's profile long after they changed their name. It returns a product price from last Tuesday when the sale ended on Wednesday. It tells your dashboard there are 42 active users when the real number is 4,200.
Stale data isn't just wrong, it's dangerous. A stale inventory count means overselling products. A stale auth token means someone who's been revoked still has access. A stale feature flag means users see a feature you turned off in production.
TTL. Time-to-Live, is the expiration date for cached data. It says: "This entry is valid for X seconds. After that, throw it away and get a fresh copy." Simple concept. But getting the number right? That's where the real engineering happens.
A is a timer attached to a cache entry. When you store data in the cache, you say: "Keep this for 300 seconds." The cache records the current timestamp plus 300 seconds as the expiration time. When someone requests that data, the cache checks: has the expiration time passed? If not, serve it. If so, it's expired, treat it as a cache miss.
Two eviction approaches:
Passive expiration (lazy). The cache doesn't actively monitor entries. It only checks expiration when someone requests the key. If the entry is expired, it gets deleted at that moment and the caller gets a miss. This is how most in-memory caches work. It's efficient, no background threads scanning for expired entries. But it means expired entries can sit in memory consuming space until someone asks for them.
Active expiration (eager). A background process periodically scans the cache and removes expired entries. does this, it randomly samples keys 20 times per second and deletes the expired ones. This keeps memory usage tighter but costs CPU cycles.
Most caches use a combination of both: active expiration handles the bulk cleanup, and passive expiration catches anything the scanner missed.
What happens on expiry:
The gap between step 1 and step 3 is the window where the data is being refreshed. During this window, the first request is slow (cache miss), but all subsequent requests within the new TTL are fast again. Some systems use a "refresh-ahead" strategy to eliminate even that brief slow period, but we'll cover that in the prefetching lesson.
This is the hard part. Set it too short and you're barely at all, the database still gets hammered. Set it too long and users see stale data.
There's no universal answer, but here's a framework:
| Data Type | Suggested | Reasoning |
|---|---|---|
| Static configuration | 1 hour - 24 hours | Changes rarely, manual deploy usually |
| Product catalog | 5-15 minutes | Changes occasionally, slight staleness is OK |
| User profile | 1-5 minutes | Users notice their own stale data quickly |
| Search results | 30-60 seconds | Tolerable staleness, high read volume |
| Real-time pricing | 5-15 seconds | Staleness has financial consequences |
| Auth tokens | 0 (no cache) or server-validated | Security-critical, stale tokens are dangerous |
The questions to ask yourself:
How often does this data change? If it changes every hour, a 24-hour TTL means serving data that could be 23 hours stale. A 5-minute TTL means the worst case is 5 minutes stale.
What's the cost of staleness? A blog post title being 5 minutes stale? Nobody cares. An account balance being 5 minutes stale? That could be a legal issue.
What's the read-to-write ratio? If data is read 1000 times per TTL window, even a short TTL provides massive savings. If it's read twice per TTL window, the cache is barely helping.
Can you use event-driven invalidation instead? Instead of relying solely on TTL, you can explicitly delete cache entries when the underlying data changes. We'll dig deep into this in the cache invalidation lesson.
A sounds simple, set a timer, entry expires, done. But there's more going on internally. What happens between the moment data enters the cache and the moment it gets evicted? This diagram traces the full lifecycle: the initial write, cache hits during the TTL window, expiration, the cache miss that triggers a refresh, and the new entry taking its place.
The step-by-step diagram above shows the internal state at each moment. But sometimes seeing events on a real timeline helps you feel the rhythm of : long stretches of cache hits, a brief expiration gap, then the cycle resets. This timeline view puts the emphasis on time and makes the 1:60,000 ratio between database queries and cache hits tangible.
3 questions - Score 80% to pass
What happens when a cache entry's TTL expires?
You're caching a product catalog that updates about once a day. Your cache serves 10,000 reads per hour. Which TTL makes the most sense?
What's the difference between passive (lazy) and active (eager) expiration?