What does `functools.lru_cache` do, and when should you use it?

6 minintermediatefunctionsfunctoolscachingperformance

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 list or dict argument, 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=None caches 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_cache on a method caches by (self, *args), which keeps self alive 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 whole self.

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.