Implement a TTL cache with a mock clock in Python
This code creates a simple TTL cache that stores values with an expiration timestamp and allows injecting a mock time function to test expiry behavior deterministically.
Python code
40 linesimport time
from functools import wraps
class TTLCache:
def __init__(self, ttl_seconds):
self.ttl = ttl_seconds
self.cache = {}
self._now = time.time
def set_mock_time(self, mock_time_fn):
"""Inject a mock time function for testing TTL expiry."""
self._now = mock_time_fn
def get(self, key, default=None):
now = self._now()
if key in self.cache:
value, expires_at = self.cache[key]
if now < expires_at:
return value
else:
del self.cache[key] # Expired, remove
return default
def set(self, key, value):
self.cache[key] = (value, self._now() + self.ttl)
if __name__ == "__main__":
# Simulate time with a mutable clock
fake_clock = {"time": 1000.0}
cache = TTLCache(ttl_seconds=5)
cache.set_mock_time(lambda: fake_clock["time"])
cache.set("user", {"name": "Alice"})
print("Initial get:", cache.get("user"))
fake_clock["time"] += 3 # 3 seconds later — still valid
print("After 3s:", cache.get("user"))
fake_clock["time"] += 3 # 6 seconds total — expired
print("After 6s:", cache.get("user", "MISS"))
Output
Initial get: {'name': 'Alice'}
After 3s: {'name': 'Alice'}
After 6s: MISS
How it works
The TTLCache stores each value as a tuple containing the actual value and an expiration timestamp computed at set time. The get method first checks if the key exists, then compares the current time (obtained from the injected time function) against the expiration; if expired, it deletes the entry and returns the default. Injection of a mock time function (set_mock_time) allows testing TTL logic without sleeping or depending on the real system clock. This pattern keeps the cache logic pure and testable, which is especially useful in unit tests for caching layers.
Common mistakes
- Forgetting to use the injected time function consistently in both `set` and `get`
- Not handling expired entries by deleting them, causing stale data to remain
- Assuming the default `time.time` is used correctly without mocking in tests
Variations
- Use a monotonic clock like `time.monotonic` instead of wall-clock time to avoid drift
- Implement automatic background cleanup of expired entries with a background thread
Real-world use cases
- Unit testing caching layers in web applications without waiting for real time to pass.
- Simulating cache expiration in integration tests to verify fallback behavior when data is refreshed.
- Testing time-based features like session expiry or temporary authorization tokens deterministically.
Sponsored
More from Caching & Redis
- Cache Asides in Python with a Read-Through Loader easy
- Cache Data in Redis with Python easy
- Cache Penetration Null Object Mock in Python medium
- Cache Stampede Prevention with SingleFlight in Python medium
- Cache Warming with Python: Preload Hot Keys easy
- Coalescing duplicate in-flight requests: one shared result for concurrent callers hard
Keep learning
Related tutorials and quizzes for this topic.