DSA Notes
Master memoization completely — what it is, how it transforms exponential recursion to polynomial time, implementing memo dictionaries in Python, using lru_cache and functools.cache, multi-dimensional memoization, when memoization is better than tabulation, and common pitfalls.
What Is Memoization?
Memoization is the technique of storing the results of expensive function calls and returning the cached result when the same inputs occur again.
The word comes from "memo" (note) — you write down the answer so you don't have to recompute it.
Memoization is the natural way to implement dynamic programming: start with a recursive solution (which is often obvious), then add a cache to prevent recomputation.
Adding Memoization — Step by Step
What changed: One if n in memo: return memo[n] check and one memo[n] = ... assignment. That's it. The algorithm went from O(2^n) to O(n).
How it works:
- First call:
fib(5)→ callsfib(4)andfib(3) fib(4)→ callsfib(3)(not computed yet) andfib(2)(not computed yet)- When
fib(3)completes, it's stored in memo - When
fib(5)then needsfib(3)directly, it's already cached → O(1) lookup
Python's Built-in Memoization
Python provides memoization through functools:
@lru_cache — Least Recently Used Cache
from functools import lru_cache
@lru_cache(maxsize=None) # maxsize=None means unlimited cache
def fibonacci(n):
if n <= 1: return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(100)) # 354224848179261915075
print(fibonacci.cache_info()) # CacheInfo(hits=98, misses=101, maxsize=None, currsize=101)@cache — Simpler Version (Python 3.9+)
from functools import cache
@cache
def fibonacci(n):
if n <= 1: return n
return fibonacci(n-1) + fibonacci(n-2)@cache is equivalent to @lru_cache(maxsize=None) but slightly faster.
Multi-Dimensional Memoization
For problems with multiple parameters:
Note: lru_cache requires all arguments to be hashable. Use tuples (not lists) for collections.
Memoization with Dictionary (Manual)
When lru_cache isn't available or when you need more control:
Classic Memoization Problems
1. Coin Change (Minimum Coins)
2. Unique Paths in Grid
from functools import lru_cache
def unique_paths(m, n):
"""
Count paths from (0,0) to (m-1,n-1) moving only right or down.
State: (row, col)
Recurrence: dp(r,c) = dp(r-1,c) + dp(r,c-1)
"""
@lru_cache(maxsize=None)
def dp(r, c):
if r == 0 or c == 0: return 1 # Only one path along edges
return dp(r-1, c) + dp(r, c-1)
return dp(m-1, n-1)
print(unique_paths(3, 7)) # 28
print(unique_paths(3, 3)) # 63. Word Break
When Memoization Is Better Than Tabulation
Prefer Memoization when
✅ Not all states are needed (sparse state space)
Example: dp(100, 50) might never call dp(1, 99) — why compute it?
✅ Recursion is the natural way to think about the problem
✅ The state space is irregular (tree problems, graph problems)
✅ Prototyping quickly — memoization is faster to code
Prefer Tabulation when
✅ All states need to be computed anyway
✅ No recursion overhead (important for Python speed)
✅ Stack overflow is a concern (deep recursion)
✅ Space optimization needed (slide over table rows)
Common Memoization Pitfalls
Pitfall 1 — Mutable Default Argument
# WRONG: memo={} is shared across all calls!
def fib(n, memo={}):
...
# CORRECT: Use None as default, create inside
def fib(n, memo=None):
if memo is None: memo = {}
...
# OR use @lru_cache which handles this correctlyPitfall 2 — Unhashable Arguments
# WRONG: Lists are not hashable
@lru_cache(maxsize=None)
def dp(arr, target): # arr is a list — TypeError!
...
# CORRECT: Convert to tuple
def solve(arr, target):
return dp(tuple(arr), target)
@lru_cache(maxsize=None)
def dp(arr, target): # arr is now a tuple — hashable ✓
...Pitfall 3 — Not Clearing Cache Between Test Cases
# If you reuse a memoized function across multiple test cases:
fibonacci.cache_clear() # Clear cache between independent problemsComplexity Analysis with Memoization
| States: 1 per value of n | n states |
| Work per state | O(1) (just two recursive calls) |
| Total | O(n) |
| States | m × n (one per (i,j) pair) |
| Work per state | O(1) |
| Total | O(m × n) |
| States | n items × W capacity values = n × W states |
| Work per state | O(1) |
| Total | O(n × W) |
| States | n values (number of nodes) |
| Work per state | O(n) (iterate over root choices) |
| Total | O(n²) |
Summary
Memoization transforms exponential recursion into polynomial DP:
- Start with recursive solution — implement the natural recursion
- Add cache — check if result is already computed before recursing
- Store result — before returning, save to cache
In Python: Use @lru_cache(maxsize=None) or @cache (Python 3.9+) for the cleanest code.
Key insight: Memoization only helps when the same subproblem (same arguments) is called multiple times. This is precisely the "overlapping subproblems" property that makes DP applicable.
*Next Lesson: Tabulation*
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Memoization — Top-Down Dynamic Programming with Caching.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Data Structures & Algorithms topic.
Search Terms
data-structures-algorithms, data structures & algorithms, data, structures, algorithms, dynamic, programming, memoization
Related Data Structures & Algorithms Topics