TTL Cache Implementation Guide
Table of Contentsโ
- Introduction
- Core Concepts
- Basic TTL Cache (Lazy Eviction)
- TTL Cache with Auto Cleanup (Hybrid)
- Sliding TTL Cache
- TTL + LRU Cache
- Real-World Usage Patterns
- Common Interview Follow-ups
- Time & Space Complexity
- Summary
Introductionโ
A TTL-based cache (Time To Live cache) stores data with an expiry time. Once the TTL expires, the entry is considered invalid and should not be returned.
This is very common in:
- Browser caching
- API response caching
- Session storage
- Distributed caches (Redis, Memcached)
- Frontend memoization with staleness control
Core Conceptsโ
TTLโ
Duration an entry is valid (expiresAt = now + ttl)
Can be:
- Absolute TTL โ expires at a fixed time
- Sliding TTL โ refreshes on access
Expiration Strategiesโ
- Lazy eviction โ remove on get
- Eager eviction โ background cleanup
- Hybrid โ lazy + periodic cleanup (most common)
Basic TTL Cache (Lazy Eviction)โ
Requirementsโ
set(key, value, ttl)get(key)- Expired entries should not be returned
- O(1) operations
Simple Implementation (Lazy Eviction)โ
class TTLCache {
constructor() {
this.store = new Map();
}
set(key, value, ttl) {
const expiresAt = Date.now() + ttl;
this.store.set(key, { value, expiresAt });
}
get(key) {
const entry = this.store.get(key);
if (!entry) return null;
if (Date.now() > entry.expiresAt) {
this.store.delete(key);
return null;
}
return entry.value;
}
has(key) {
return this.get(key) !== null;
}
delete(key) {
this.store.delete(key);
}
}
Pros:
- Simple
- No timers
- O(1)
Cons:
- Expired entries remain until accessed
TTL Cache with Auto Cleanup (Hybrid)โ
Adds periodic eviction to prevent memory leaks.
class TTLCache {
constructor(cleanupInterval = 1000) {
this.store = new Map();
this.timer = setInterval(() => this.cleanup(), cleanupInterval);
}
set(key, value, ttl) {
this.store.set(key, {
value,
expiresAt: Date.now() + ttl,
});
}
get(key) {
const entry = this.store.get(key);
if (!entry) return null;
if (Date.now() > entry.expiresAt) {
this.store.delete(key);
return null;
}
return entry.value;
}
cleanup() {
const now = Date.now();
for (const [key, entry] of this.store) {
if (now > entry.expiresAt) {
this.store.delete(key);
}
}
}
destroy() {
clearInterval(this.timer);
}
}
Sliding TTL Cacheโ
Used for sessions, auth tokens, hot data.
class SlidingTTLCache {
constructor(ttl) {
this.ttl = ttl;
this.store = new Map();
}
set(key, value) {
this.store.set(key, {
value,
expiresAt: Date.now() + this.ttl,
});
}
get(key) {
const entry = this.store.get(key);
if (!entry) return null;
if (Date.now() > entry.expiresAt) {
this.store.delete(key);
return null;
}
// refresh TTL
entry.expiresAt = Date.now() + this.ttl;
return entry.value;
}
}
TTL + LRU Cacheโ
๐ "Design an LRU cache with TTL" - Very Common Interview Question
Key Ideaโ
- TTL โ validity
- LRU โ eviction when capacity exceeded
Data Structuresโ
- Map โ O(1) lookup
- Doubly Linked List โ O(1) recency updates
- TTL is checked on get before returning
Simplified LRU + TTL (Map-only version)โ
class LRUCacheWithTTL {
constructor(capacity) {
this.capacity = capacity;
this.map = new Map();
}
set(key, value, ttl) {
if (this.map.has(key)) {
this.map.delete(key);
}
this.map.set(key, {
value,
expiresAt: Date.now() + ttl,
});
if (this.map.size > this.capacity) {
const lruKey = this.map.keys().next().value;
this.map.delete(lruKey);
}
}
get(key) {
const entry = this.map.get(key);
if (!entry) return null;
if (Date.now() > entry.expiresAt) {
this.map.delete(key);
return null;
}
// mark as recently used
this.map.delete(key);
this.map.set(key, entry);
return entry.value;
}
}
Real-World Usage Patternsโ
Frontendโ
- Cache API responses
- Avoid refetch for X seconds
- Stale-while-revalidate pattern
if (cache.get(url)) return cache.get(url);
const data = await fetch(url).then(r => r.json());
cache.set(url, data, 5000);
Backend / Distributed Cacheโ
- Redis
SET key value EX 60 - Memcached TTL
- CDN edge caching
Common Interview Follow-upsโ
โ Why TTL instead of manual invalidation?
- Avoid stale data
- Works well when updates are unpredictable
โ Lazy vs Eager eviction?
| Strategy | When |
|---|---|
| Lazy | Low traffic |
| Eager | Memory-sensitive |
| Hybrid | Production systems |
โ Clock skew issue?
- Use server time
- Distributed systems often use relative TTL
Time & Space Complexityโ
| Operation | Complexity |
|---|---|
| get | O(1) |
| set | O(1) |
| cleanup | O(n) (periodic) |
Summaryโ
- TTL cache = cache with expiry
- Lazy eviction is simplest
- Sliding TTL is common for sessions
- TTL + LRU is interview gold
- Map + timestamps = 90% of real implementations