functools.cache vs lru_cache
functools.cache vs lru_cache
Thesis
functools.lru_cache remembers recent calls up to maxsize. functools.cache is the same idea with maxsize=None — unbounded. Both make recursive Fibonacci trivial; only one can grow without bound and OOM a long-running process. This chapter times both on fib(35) and then fills an unbounded cache with 10 000 distinct keys.
Mental model
lru_cache(maxsize=128) → bounded; evicts least-recently used
cache() → alias for lru_cache(maxsize=None); never evicts
Worked examples
Verified with CPython 3.15.0.
import functools, time
@functools.lru_cache(maxsize=128)
def fib_lru(n):
return n if n < 2 else fib_lru(n-1)+fib_lru(n-2)
@functools.cache
def fib_cache(n):
return n if n < 2 else fib_cache(n-1)+fib_cache(n-2)Output:
3.15.0 (main, Oct 9 2026, 15:24:40) [Clang 22.1.3 ]
lru_cache fib(35)=9227465 in 0.01 ms CacheInfo(hits=33, misses=36, maxsize=128, currsize=36)
cache fib(35)=9227465 in 0.01 ms CacheInfo(hits=33, misses=36, maxsize=None, currsize=36)
cache after 10k distinct keys: CacheInfo(hits=0, misses=10000, maxsize=None, currsize=10000)
fib(35) is instant for both after memoization. The unbounded cache after 10 k distinct keys reports currsize=10000 with maxsize=None — that growth is the point.
Trap
Caching functions of unbounded arguments in servers (cache on raw request URLs, SQL, or user ids). Prefer lru_cache with an explicit cap, or a proper TTL cache.
Boring rule
Default to lru_cache(maxsize=…) with a number you can defend. Use cache only for pure functions over a small finite domain.
Try this
- Compare
cache_info().hitsunder a realistic call mix. - Add
typed=Trueand call with1vs1.0. - Read the docs for
cache_clearin tests.