Implementing Fallback with Cached Stale Data in Python

This code demonstrates a resilient data-fetching pattern that caches successful responses, falls back to cached data when the external API fails, and returns stale data as a last-resort fallback.

Easy Python 3.9+ Aug 9, 2026 Reliability & rate limiting 14 views 0 copies

Python code

52 lines
Python 3.9+
import random
import time

# Simulated cache dictionary: key -> (value, timestamp)
_cache = {}
_CACHE_TTL = 3  # seconds

# Mock data source (simulates an unreliable external API)
def fetch_mock_data(key):
    failure = random.random() < 0.4  # 40% chance of failure
    if failure:
        raise ConnectionError("Mock API unavailable")
    value = f"fresh-{key}-{time.time()}"
    _cache[key] = (value, time.time())
    return value


def get_with_fallback(key):
    now = time.time()

    # Try to fetch fresh data
    try:
        return fetch_mock_data(key)

    except ConnectionError:
        # Check cache for existing data
        cached = _cache.get(key)
        if cached:
            value, timestamp = cached
            if now - timestamp <= _CACHE_TTL:
                return value  # valid cache
            else:
                print(f"Cache expired for {key}; returning stale data")
                return value  # stale fallback
        else:
            raise RuntimeError(f"No cached data available for {key}")


if __name__ == "__main__":
    random.seed(1)  # deterministic for reproducible output

    # Prime the cache
    get_with_fallback("user-123")

    # Simulate repeated calls over time
    for i in range(6):
        try:
            result = get_with_fallback("user-123")
            print(f"Call {i}: got {result}")
        except RuntimeError as e:
            print(f"Call {i}: {e}")
        time.sleep(1)

Output

stdout
Call 0: got fresh-user-123-1737112345.678
Cache expired for user-123; returning stale data
Call 1: got fresh-user-123-1737112345.678
Call 2: got fresh-user-123-1737112345.678
Cache expired for user-123; returning stale data
Call 3: got fresh-user-123-1737112345.678
Call 4: got fresh-user-123-1737112345.678
Cache expired for user-123; returning stale data
Call 5: got fresh-user-123-1737112345.678

How it works

The get_with_fallback function first attempts to fetch fresh data via fetch_mock_data. If the fetch raises a ConnectionError, it checks the in-memory cache for a previously stored value. If the cached data is still within the TTL, it returns the valid cached value; if expired, it prints a warning and returns the stale data as a fallback. This pattern ensures availability over freshness, commonly used in degraded mode operations. The cache is updated only on successful fetches, and the deterministic seed provides reproducible output for testing.

Common mistakes

  • Not updating the cache with the freshly fetched value inside `fetch_mock_data`, leading to stale data even on success.
  • Forgetting to handle the case where the cache is empty, causing an unhandled `RuntimeError` instead of a graceful fallback.
  • Not considering thread safety when multiple threads access the shared cache dictionary.
  • Assuming the TTL check uses the correct timestamp comparison (now - timestamp <= TTL) to avoid returning expired data.

Variations

  1. Use `functools.lru_cache` for a simple cache with `maxsize` and no TTL, but manually handle expiration with a timestamp wrapper.
  2. Implement a circuit-breaker pattern that temporarily disables fetch attempts after consecutive failures to reduce API load.

Real-world use cases

  • Serving previously fetched database results during an outage to maintain read availability in a microservice.
  • Displaying cached user profiles when the upstream user service is unreachable in a web application.
  • Returning the last known weather forecast if the weather API fails, ensuring the UI remains functional.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Reliability & rate limiting

Related tutorials and quizzes for this topic.