How to Memoize Pure Functions with functools.lru_cache in Python
Use functools.lru_cache to memoize a pure Fibonacci function and avoid recomputing repeated values.
Python code
17 linesfrom functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
"""Return the nth Fibonacci number (0-indexed) using memoization."""
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
if __name__ == "__main__":
for i in range(10):
print(f"fibonacci({i}) = {fibonacci(i)}")
print(f"Cache info: {fibonacci.cache_info()}")
print(f"Cache hit rate: {fibonacci.cache_info().hits / fibonacci.cache_info().currsize:.2f}")
Output
fibonacci(0) = 0
fibonacci(1) = 1
fibonacci(2) = 1
fibonacci(3) = 2
fibonacci(4) = 3
fibonacci(5) = 5
fibonacci(6) = 8
fibonacci(7) = 13
fibonacci(8) = 21
fibonacci(9) = 34
Cache info: CacheInfo(hits=8, misses=10, maxsize=128, currsize=10)
Cache hit rate: 0.80
How it works
The @lru_cache decorator wraps fibonacci so that results for each argument are stored in an internal dictionary. On subsequent calls with the same input, the cached value is returned instantly instead of recalculating. This turns the exponential recursion of naive Fibonacci into an almost linear approach. The decorator also exposes cache_info() to monitor hits, misses, and current cache size — useful for performance tuning. Because fibonacci is a pure function (same inputs always give the same outputs), the cache never causes stale results.
Common mistakes
- Forgetting that decorated functions are cached — mutable default arguments or non-hashable inputs will raise TypeError.
- Ignoring the global `maxsize` default of 128; if you expect more unique inputs, set a larger maxsize or use `maxsize=None` for unbounded caching.
- Not using `cache_clear()` when the computation depends on external state that can change over time.
Variations
- Use `functools.cache` (Python 3.9+) as a simpler alias when you don't need a size limit.
- Use `@lru_cache(maxsize=None)` for unbounded memoization if memory is not a concern.
Real-world use cases
- Speeding up recursive algorithms like the Fibonacci sequence or dynamic programming problems in coding interviews.
- Caching results of expensive, deterministic API calls (e.g., parsing a large config file) so repeated requests don't hit the disk or network.
- Memoizing hash computations or cryptographic signatures where identical inputs occur frequently in batch processing.
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.