19. LRU Cache for Voice Session State
Problem
A voice agent keeps a small in-memory blob of per-session state (detected language, turn count, running transcript offset) so it doesn't have to re-derive it from scratch on every audio chunk. Memory is bounded, so only the most recently touched sessions can be kept warm; the rest fall back to a colder store (out of scope here).
Implement a fixed-capacity cache where both reading and writing a session count as "recently used," and the least-recently-used session is evicted first when the cache is full.
Source: src/19_voice_session_lru_cache.py
class VoiceSessionCache:
def __init__(self, capacity: int): ...
def get(self, session_id: str) -> dict | None: ...
def put(self, session_id: str, state: dict) -> None: ...
>>> c = VoiceSessionCache(2)
>>> c.put("s1", {"lang": "en"})
>>> c.put("s2", {"lang": "fr"})
>>> c.get("s1")
{'lang': 'en'}
>>> c.put("s3", {"lang": "de"})
>>> c.get("s2")
Step-by-Step Approach
- Recognize that both operations need O(1) time: a plain dict alone gives O(1) lookup but no ordering, and a plain list gives ordering but O(n) reordering — the classic fix is a hash map plus a structure that supports O(1) move-to-front/back.
- In Python,
collections.OrderedDictgives both for free: it's a hash map that also remembers insertion/move order, withmove_to_endas an O(1) reordering primitive. - On
get: if the key is missing, returnNone; otherwise callmove_to_endto mark it most-recently-used, then return its value. - On
put: if the key already exists, move it to the end first (so overwriting a value still refreshes its recency); then set/overwrite the value. - After inserting, check if the cache exceeds capacity; if so, evict the entry at the front of the ordering (
popitem(last=False)) — that's always the true least-recently-used entry because every touch moves a key to the back.
The key insight is that "least recently used" is really just "maintain a total order by recency with O(1) reordering," and a doubly linked list (which is what OrderedDict is internally) is exactly the structure that supports moving an arbitrary node to either end in O(1), while a companion hash map gives O(1) access to that node by key.
Reference solution
from collections import OrderedDict
class VoiceSessionCache:
def __init__(self, capacity: int):
self.capacity = capacity
# OrderedDict tracks insertion/access order directly: front = LRU,
# back = MRU. get/put both call move_to_end to mark "recently used".
self.store: OrderedDict[str, dict] = OrderedDict()
def get(self, session_id: str) -> dict | None:
if session_id not in self.store:
return None
self.store.move_to_end(session_id) # mark as most-recently-used
return self.store[session_id]
def put(self, session_id: str, state: dict) -> None:
if session_id in self.store:
self.store.move_to_end(session_id)
self.store[session_id] = state
if len(self.store) > self.capacity:
# popitem(last=False) removes the front of the ordering, i.e. the LRU entry
self.store.popitem(last=False)
Key Functions & Tricks
collections.OrderedDict— hash map with a built-in doubly-linked ordering; the go-to Python primitive for LRU without hand-rolling a linked list.move_to_end(key)— O(1) relocation of a key to the most-recently-used end.popitem(last=False)— O(1) pop from the least-recently-used end.- Touching order on both
getandput— the detail that makes it LRU rather than FIFO; forgetting to refresh on read is the most common bug.
How to Recognize This Pattern
This is the classic LRU cache pattern: recognize it whenever a problem needs O(1) get/put with capacity-bounded eviction of the least-recently-touched item — session caches, connection pools, and semantic/embedding caches all reduce to it. A common variation is LFU (evict least-frequently used instead of least-recently), which needs a frequency-bucketed structure instead of a single ordering. A common pitfall is only refreshing recency on write and not on read (silently turning it into a write-recency cache), or implementing the ordering with a plain list/dict and accidentally paying O(n) per operation instead of O(1).