Python @lru_cache explained: maxsize=None, vs @cache, examples
@lru_cache(maxsize=None) memoizes a function with an unbounded cache: each distinct set of arguments is computed once, every repeat call returns the stored result, and nothing is ever evicted. It turns recursive Fibonacci from a call that never finishes into one that returns instantly:
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n): return n if n < 2 else fib(n - 1) + fib(n - 2)
With the decorator, fib(300) prints a 63-digit number immediately; without it the same call would need roughly 2³⁰⁰ recursive calls and would never return. Measured on CPython 3.12: fib(32) takes 0.37 s plain and 0.00002 s cached (the harness is in the next section). @lru_cache(None) is the same thing — maxsize is the first positional parameter — and since Python 3.9 @cache is the same thing again under a shorter name. The rest of this post covers the bounded form @lru_cache(maxsize=N), when to prefer @cache, and how to inspect and clear the cache.
Measure the speedup yourself
import timeit
from functools import lru_cache
def fib_plain(n):
return n if n < 2 else fib_plain(n - 1) + fib_plain(n - 2)
@lru_cache(maxsize=None)
def fib_cached(n):
return n if n < 2 else fib_cached(n - 1) + fib_cached(n - 2)
print(f"plain fib(32): {timeit.timeit(lambda: fib_plain(32), number=1):.3f} s")
print(f"cached fib(32): {timeit.timeit(lambda: fib_cached(32), number=1):.6f} s")
print(fib_cached.cache_info())
# plain fib(32): 0.371 s
# cached fib(32): 0.000021 s
# CacheInfo(hits=30, misses=33, maxsize=None, currsize=33)
The plain version makes about 7 million calls; the cached one makes 63 (33 misses that ran the body, 30 hits served from the cache). Your absolute numbers will differ, the ratio will not.
The basic example
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(40))
Without caching, recursive Fibonacci repeats the same branches exponentially. With lru_cache, each input is calculated once. The decorator stores a mapping from function arguments to return values.
What does LRU mean?
LRU means least recently used. By default, the cache has a limit. When it is full, Python removes the result that has not been used for the longest time. Set maxsize=None for an unbounded cache, but only when the input space is safely bounded or the process has enough memory.
@lru_cache(maxsize=128)
def exchange_rate(currency):
# expensive lookup or calculation
return load_rate(currency)
Use a small maxsize when there are many possible arguments and only recent values are useful.
Inspect and clear the cache
Every cached function exposes simple diagnostics:
print(exchange_rate.cache_info())
# CacheInfo(hits=10, misses=3, maxsize=128, currsize=3)
exchange_rate.cache_clear()
Hits show how often the cache saved work. Misses show how often the function still had to run. If hits are near zero, caching may not help—or the arguments may be changing every time.
Rules that surprise people
Arguments must be hashable, so lists and dictionaries cannot be used directly as cached arguments. Convert a list to a tuple or design the function around immutable inputs. The cache also treats some distinct argument combinations separately, so keep calls consistent.
Caching is process-local. It does not persist across restarts, and separate web workers have separate caches. Do not use it as a database or as a replacement for a shared cache such as Redis.
Avoid caching functions with side effects, random output, current-time reads, or results that depend on hidden global state. A cache assumes the same arguments produce the same answer.
@lru_cache vs @cache
Python 3.9 added functools.cache, which is exactly lru_cache(maxsize=None) under a shorter name: an unbounded cache with no eviction and slightly less bookkeeping overhead.
from functools import cache
@cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
The decision rule is simple. Use @cache when the set of possible arguments is small and bounded — recursion depths, enum-like lookups, parsed constants. Use @lru_cache(maxsize=N) when arguments keep arriving from the outside world (user IDs, URLs, query strings) and you need the cache to stop growing at some point. An unbounded cache fed unbounded inputs is a memory leak with a decorator on top.
When should you use lru_cache?
Use it for deterministic, relatively expensive functions with repeated inputs: recursive dynamic programming, parsing stable configuration, converting immutable values, and bounded lookup work. Measure before and after with cache_info(); a cache that never hits adds memory and lookup overhead without improving performance.
Go deeper
Reading about caching is recognition; building one is recall. The LRU cache implementation lesson walks you through writing the data structure yourself — an ordered dict, O(1) get and put, the eviction logic functools hides from you. It is also one of the most common medium-difficulty interview questions.
Try it: paste the cached and uncached Fibonacci versions into the free browser editor and compare timings yourself — no install, no card. Or start the Python track from lesson one.