Question presented to candidate:
"Here is a slow recursive Fibonacci. Make it fast with one line from the standard library, then tell me how functools.lru_cache actually works: what is the key, what happens when it is full, and when is it a bad idea?"
What a strong answer should cover:
- Memoisation: results are stored in a dict keyed by the call arguments, so a repeated call returns the stored result; recursive Fibonacci drops from exponential to linear calls.
- The key: built from positional and keyword arguments as passed (so
f(1, b=2)andf(a=1, b=2)are separate entries); arguments must be hashable;typed=Trueseparates3from3.0. - LRU eviction: with
maxsizeentries (default 128) the least recently used one is dropped;maxsize=Noneorfunctools.cache(3.9+) never evicts. - Introspection:
cache_info()(hits, misses, maxsize, currsize),cache_clear(),cache_parameters(), and__wrapped__to bypass the cache. - Pitfalls: only for pure functions; cached mutable results are shared; on methods
selfis part of the key and instances are kept alive. - CPython detail: the wrapper is a C object (
_lru_cache_wrapper), a hash table plus a doubly linked list, and the cache structure stays consistent under threads, though the function may run more than once.
Clarifying questions expected:
- Are the arguments hashable, and is the function pure (no time, I/O or global state)?
- How many distinct inputs are there, and how much memory can the cache use?
Code / implementation expected: Yes β decorate a recursive function and show cache_info(); optionally demonstrate eviction with a small maxsize.