What does `functools.lru_cache` do, and when should you use it?
Quick Answer
`@functools.lru_cache(maxsize=...)` memoizes a function's return value keyed by its arguments, so repeated calls with the same (hashable) arguments return the cached result instantly instead of recomputing. It's ideal for pure, deterministic functions with expensive computation and a bounded set of distinct inputs — e.g., recursive algorithms (Fibonacci), or repeated lookups — but the arguments must be hashable, and it can leak memory if used carelessly on methods holding a reference to `self`.
Detailed Answer
Basic usage: memoizing an expensive pure function
import functools
@functools.lru_cache(maxsize=128)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
fib(35) # fast -- each unique n is computed once, then cached
fib.cache_info() # CacheInfo(hits=32, misses=36, maxsize=128, currsize=36)
Without caching, naive recursive fib is exponential (recomputes the same
sub-calls repeatedly). lru_cache turns it into linear time by
remembering every fib(n) result already computed — an "LRU" (least
recently used) eviction policy discards the oldest entries once maxsize
is exceeded (or never evicts, if maxsize=None).
Requirements and gotchas
- Arguments must be hashable — you can't cache a call with a
listordictargument, since the cache key is built from the arguments themselves (as if put in a dict). Pass tuples or frozensets instead. - Only for pure functions — the cache doesn't know if the function has side effects or depends on external mutable state; caching a function whose result depends on something other than its arguments (current time, a database row that can change) will return stale results.
- Memory:
maxsize=Nonecaches unboundedly — fine for a small, fixed input domain, dangerous for functions called with unbounded/unique arguments (e.g., caching by user-supplied string with no upper bound). - Caching instance methods:
@lru_cacheon a method caches by(self, *args), which keepsselfalive for as long as the cache entry exists — this can prevent garbage collection of instances you expected to be freed. A common workaround is caching a module-level function that takes only the needed hashable data instead of the wholeself.
cache vs lru_cache
@functools.cache # Python 3.9+, equivalent to lru_cache(maxsize=None)
def expensive(n):
...
functools.cache is a simpler alias for an unbounded cache, useful when
you know the input domain is naturally small/finite and don't need
eviction.
Interview-ready summary: lru_cache memoizes pure functions keyed by
their (hashable) arguments, turning repeated/recursive expensive
computation into cheap lookups — but it requires hashable arguments,
assumes no side effects or external state dependence, and can extend an
object's lifetime unexpectedly when applied to instance methods.