How to Use functools.cache for Unbounded Memoization in Python
Speed up repeated recursive calls by memoizing function results with Python's built-in functools.cache decorator.
Python code
20 lines```python
import functools
import time
@functools.cache
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
if __name__ == "__main__":
start = time.perf_counter()
result = fib(30)
elapsed = time.perf_counter() - start
print(f"fib(30) = {result}")
print(f"computed in {elapsed:.6f} seconds")
print(f"cache info: {fib.cache_info()}")
Output
fib(30) = 832040
computed in 0.000021 seconds
cache info: CacheInfo(hits=58, misses=31, maxsize=None, currsize=31)
How it works
functools.cache is a thin wrapper around functools.lru_cache(maxsize=None) that caches every result indefinitely — there is no eviction and the cache never shrinks. When fib is first called with n=30, the decorator intercepts each unique argument, stores the return value, and reuses it for any repeated n in the recursion tree. This collapses the exponential call count (over 2.6 million calls without caching) down to just 31 unique computations, which is why the result appears almost instantly. The cache_info() method exposes useful stats: how many times cached values were used (hits) versus how many times the function actually ran (misses). Because the cache lives on the function object, it persists for the life of the process unless you call fib.cache_clear().
Common mistakes
- Forgetting that functools.cache was added in Python 3.9 — earlier versions need lru_cache(maxsize=None).
- Using it on functions with mutable or unhashable arguments, like lists or dicts, which cause a TypeError.
- Assuming memory is unbounded is fine — long-running apps with millions of unique inputs can exhaust RAM and should set a maxsize instead.
Variations
- Use `@functools.lru_cache(maxsize=128)` to bound the cache and evict the least-recently-used entries.
- Reach for `@functools.cached_property` to memoize a computed attribute on a class instance instead of a plain function.
Real-world use cases
- Speeding up recursive algorithms like Fibonacci, factorial, or edit-distance calculations in interview-style coding.
- Memoizing expensive pure function results in data pipelines, such as fetching or computing the same value across many records.
- Caching parsed configuration or URL metadata across request handlers in a web service to avoid repeated I/O.
Sponsored
More from Concurrency & performance
- Benchmark list.append vs deque.append in Python medium
- Build a Python Performance Profiler That Generates Readable Reports medium
- Graceful Shutdown Executor Context Manager in Python medium
- How to Build a Producer-Consumer Pattern with asyncio.Queue in Python medium
- How to Cancel an asyncio Task with Graceful Cleanup in Python medium
- How to Convert Data in Parallel with ThreadPoolExecutor in Python easy
Keep learning
Related tutorials and quizzes for this topic.