How to Build a TTL Cache Dict in Python

Create a dictionary subclass that automatically expires keys after a fixed time-to-live using timestamps.

Medium Python 3.9+ Aug 9, 2026 Dictionaries & sets 16 views 0 copies

Python code

44 lines
Python 3.9+
import time

class TTLDict(dict):
    def __init__(self, ttl, *args, **kwargs):
        self.ttl = ttl
        self._expires = {}
        super().__init__(*args, **kwargs)

    def __setitem__(self, key, value):
        super().__setitem__(key, value)
        self._expires[key] = time.time() + self.ttl

    def __getitem__(self, key):
        if self._is_expired(key):
            self._delete_expired(key)
            raise KeyError(key)
        return super().__getitem__(key)

    def __contains__(self, key):
        if self._is_expired(key):
            self._delete_expired(key)
            return False
        return super().__contains__(key)

    def get(self, key, default=None):
        try:
            return self[key]
        except KeyError:
            return default

    def _is_expired(self, key):
        return key in self._expires and time.time() > self._expires[key]

    def _delete_expired(self, key):
        del dict.__getitem__(self, key)
        del dict.__getattribute__(self, '_expires')[key]

if __name__ == "__main__":
    cache = TTLDict(2)
    cache["name"] = "Alice"
    print(f"immediate: {cache.get('name')}")
    time.sleep(3)
    print(f"after 3s: {cache.get('name')}")
    print(f"in dict: {'name' in cache}")

Output

stdout
immediate: Alice
after 3s: None
in dict: False

How it works

The TTLDict class extends the built-in dict and tracks expiration times in a separate _expires map. On every __setitem__, it stores a timestamp equal to now + ttl; lookups and membership checks call _is_expired and purge stale entries. The overridden get returns a default instead of raising KeyError when a key has expired, which keeps callers safe. Because dict is written in C, the class must super().__getattribute__ to reach _expires in deletion logic — a subtle but important detail.

Common mistakes

  • Using `self._expires` inside `__getattribute__` or `__delitem__` causes infinite recursion when the attribute isn't initialized.
  • Forgetting to handle expired keys in `__contains__`, leaving stale data visible to `if key in cache` checks.
  • Not clearing `_expires` after `pop` or `del`, leaking memory over time.

Variations

  1. Use `collections.OrderedDict` to evict oldest entries before TTL expires.
  2. Implement a background thread that periodically purges expired keys instead of lazily checking on access.

Real-world use cases

  • Caching database query results for a short period to reduce load in a web service.
  • Storing session tokens or API keys that must be invalidated after a timeout.
  • Temporarily memoizing expensive function results in data pipelines.

Sponsored

Run this sample

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

Open editor

More from Dictionaries & sets

Related tutorials and quizzes for this topic.