Memoization
Cache the results of expensive function calls to avoid recomputing subproblems
Overview
Memoization stores the result of a function for a given set of arguments so repeated calls return instantly. It is the top-down flavor of dynamic programming: keep the natural recursive structure but cache overlapping subproblems. This can collapse exponential recursion into linear time at the cost of O(n) extra space for the cache.
Syntax / Usage
Wrap a recursive function with a cache keyed by its arguments. Python's functools.lru_cache does this automatically, or you can pass an explicit dictionary.
from functools import lru_cache
@lru_cache(maxsize=None) # O(n) time and space after memoization
def fib(n):
if n < 2: # base cases
return n
return fib(n - 1) + fib(n - 2) # each n computed once, then cached
Examples
Without a cache, fib is exponential; memoization makes large inputs instant:
print(fib(50)) # 12586269025 (computed in linear time)
Manual memoization with a dict, useful when arguments are unhashable or you want control:
def climb_stairs(n, memo=None): # ways to climb n stairs 1 or 2 at a time
if memo is None:
memo = {}
if n <= 2:
return n
if n not in memo:
memo[n] = climb_stairs(n - 1, memo) + climb_stairs(n - 2, memo)
return memo[n]
print(climb_stairs(10)) # 89
Common Mistakes
- Using a mutable default argument like
memo={}at the top level, which is shared across calls. - Caching on unhashable arguments (lists, dicts) without converting them to tuples first.
- Applying
lru_cacheto functions with side effects, hiding bugs behind cached returns. - Letting an unbounded cache grow forever in long-running processes; set a
maxsize. - Memoizing problems without overlapping subproblems, where the cache adds overhead for no gain.
See Also
algorithms-dynamic-programming algorithms-recursion algorithms-big-o-notation