Memoization vs Tabulation
Dynamic programming can be implemented in two equivalent styles: top-down with memoization (recursive + cache) or bottom-up with tabulation (iterative table fill). Both store subproblem answers—the difference is order of computation and coding style.
Overview
Both approaches solve the same DP recurrence. Memoization starts from the full problem and lazily fills a cache when subproblems are needed. Tabulation starts from base cases and builds the table forward until the final answer is known.
Top-down (memoization) Bottom-up (tabulation) ───────────────────── ───────────────────── solve(n) dp[0], dp[1] = base cases └─ needs solve(n-1) for i = 2 … n: └─ needs solve(n-2) dp[i] = f(dp[i-1], dp[i-2]) cache results on return return dp[n] return cached if seen
Top-Down: Memoization
Write the natural recursive solution, then store each state’s result in an array or hash map before returning. Unvisited states are never computed.
Pros
- Maps directly from recurrence to code
- Computes only required states
- Fast to prototype in interviews
Cons
- Recursion stack overhead
- Risk of stack overflow on deep states
- Harder to optimize space in some problems
Bottom-Up: Tabulation
Fill a DP table iteratively from smallest subproblems upward. You must determine the correct loop order so dependencies are ready before they are used.
Pros
- No recursion — no stack overflow
- Often easier to shrink space to O(1) or O(n)
- Predictable iteration — good for tight limits
Cons
- Must figure out fill order upfront
- May compute states never needed
- Slightly more setup for 2D+ tables
Side-by-Side Comparison
| Factor | Memoization (Top-Down) | Tabulation (Bottom-Up) |
|---|---|---|
| Direction | Start at target, recurse down | Start at base, iterate up |
| Implementation | Recursion + cache | Loops + array/table |
| Ease of coding | Easier from recurrence | Harder — need fill order |
| States computed | Only those reachable | Often all states in table |
| Time | Same Big O (typically) | Same Big O (typically) |
| Space | Cache + call stack | Table (can often compress) |
| Debugging | Harder (recursion tree) | Easier (inspect table) |
| Best for | Quick solve, sparse states | Large n, space limits |
Fibonacci Example
Both versions compute the same sequence; compare structure and storage.
Memoization (top-down)
def fib_memo(n, memo=None):
if memo is None:
memo = {}
if n <= 1:
return n
if n not in memo:
memo[n] = fib_memo(n - 1, memo) + fib_memo(n - 2, memo)
return memo[n]
Tabulation (bottom-up)
def fib_tab(n):
if n <= 1:
return n
dp = [0] * (n + 1)
dp[1] = 1
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
Space-optimized tabulation (O(1) extra space)
def fib_tab_optimized(n):
if n <= 1:
return n
prev2, prev1 = 0, 1
for _ in range(2, n + 1):
prev2, prev1 = prev1, prev1 + prev2
return prev1
n; tabulation fills indices 0…n. Time is O(n) for both; naive recursion without cache is O(2ⁿ).
Real-World Example Comparison
Tracing fib(5) side by side shows how memoization prunes repeated work while tabulation fills every cell in order.
Detailed Fibonacci with Call Stack Visualization
Memoization call flow:
fib(5)
├── fib(4)
│ ├── fib(3)
│ │ ├── fib(2)
│ │ │ ├── fib(1) → 1 (cached)
│ │ │ └── fib(0) → 0 (cached)
│ │ └── fib(1) → 1 (cached)
│ └── fib(2) → 1 (cached)
└── fib(3) → 2 (cached)
Total recursive calls: 9 (vs 15 without memoization)
States cached: {0, 1, 2, 3, 4, 5}
Tabulation fill order:
dp[0] = 0 dp[1] = 1 dp[2] = dp[1] + dp[0] = 1 + 0 = 1 dp[3] = dp[2] + dp[1] = 1 + 1 = 2 dp[4] = dp[3] + dp[2] = 2 + 1 = 3 dp[5] = dp[4] + dp[3] = 3 + 2 = 5 Total iterations: 5 (n − 1) States computed: all from 0 to n
When to Use Each Approach
Use this decision framework once you have a clear recurrence. Both styles are valid—the choice depends on constraints, state shape, and whether you need a quick draft or a production-ready solution.
Decision Flowchart
Start here: Do you have a clear recurrence?
| Question | Yes → | No → |
|---|---|---|
| Is recursion depth safe? | Consider memoization | Use tabulation |
| Is state space sparse? | Use memoization | Use tabulation |
| Need optimal space? | Use tabulation | Memoization may work |
| Complex table dependencies? | Use tabulation | Memoization easier |
| Interview quick draft? | Start with memoization | Then convert to tabulation |
Practical Scenarios
Scenario 1: Quick interview solution
# Start with memoization for clarity
def solve_memo(state, memo):
if base_case:
return value
if state in memo:
return memo[state]
result = recurrence(state)
memo[state] = result
return result
Scenario 2: Production code (large input)
# Use tabulation with space optimization
def solve_tab(n):
if n <= 1:
return n
prev2, prev1 = 0, 1
for i in range(2, n + 1):
curr = prev1 + prev2
prev2, prev1 = prev1, curr
return prev1
Scenario 3: Complex state (multiple dimensions)
# Tabulation with careful iteration order
def solve_2d(m, n):
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + cost[i][j]
return dp[m][n]
Space Optimization
Tabulation often allows rolling arrays: keep only the previous row or last two values when the recurrence depends on recent states only.
- 1D DP: Fibonacci, climbing stairs — O(n) table → O(1) with two variables
- 2D DP: Unique paths — O(m×n) table → O(n) with one row
- Memoization: Cache size equals distinct visited states; cannot always drop the stack
Common Interview Questions by Approach
Questions Where Memoization is Preferred
- Word Break — only need to check specific positions
- Word Break II — need to reconstruct paths; DFS + memo
- Decode Ways — natural recursive structure
- Coin Change — recursive formulation is intuitive
- Regular Expression Matching — complex recurrence, sparse states
Questions Where Tabulation is Preferred
- Unique Paths — simple grid iteration
- Minimum Path Sum — 2D grid DP
- Longest Common Subsequence — table visualization
- Edit Distance — clear 2D table
- Knapsack — weight iteration order
- Maximum Subarray — Kadane's algorithm (O(1) space)
Questions Where Either Works Equally Well
- Climbing Stairs — simple 1D DP
- House Robber — linear DP
- Fibonacci — classic example
- Jump Game — greedy works best, but DP works too
Performance Metrics Comparison
Time Complexity Breakdown
| Operation | Memoization | Tabulation |
|---|---|---|
| Initialization | O(1) | O(n) or O(n²) |
| State computation | O(number of visited states) | O(number of all states) |
| Overhead | Recursion + dict lookup | Loop + array access |
| Cache lookup | O(1) average | Direct array access O(1) |
| Function calls | More (recursive) | Fewer (iterative) |
Space Complexity Comparison
| Type | Memoization | Tabulation | Optimized Tabulation |
|---|---|---|---|
| 1D DP | O(n) cache + O(n) stack | O(n) array | O(1) variables |
| 2D DP | O(m×n) cache + O(m+n) stack | O(m×n) table | O(n) or O(m) |
| Complex state | O(visited states) | O(total states) | Often impossible |
Real-World Performance Test (Fibonacci n = 40)
| Approach | Time (ms) | Space | Calls |
|---|---|---|---|
| Naive recursion | 5000+ | O(n) | 2ⁿ |
| Memoization | <1 ms | O(n) | 2n |
| Tabulation | <1 ms | O(n) | n |
| Space-optimized | <1 ms | O(1) | n |
Advanced Techniques
1. Lazy Tabulation (Hybrid Approach)
Start with recursion but use iterative fill for performance-critical sections.
def hybrid_dp(n):
# Use memoization for exploration
# But use tabulation for known patterns
def explore(state):
if state in memo:
return memo[state]
if state < threshold:
# Use precomputed tabulation
return precomputed[state]
result = recurrence(state)
memo[state] = result
return result
2. Memoization with LRU Cache (Python)
from functools import lru_cache
@lru_cache(maxsize=None)
def fib_lru(n):
if n <= 1:
return n
return fib_lru(n - 1) + fib_lru(n - 2)
3. Iterative with History (For Debugging)
def dp_with_history(n):
dp = [0] * (n + 1)
choices = [None] * (n + 1) # Track decisions
for i in range(1, n + 1):
# Record which choice was made
if take_option:
dp[i] = best_value
choices[i] = "took option"
# Reconstruct solution
path = reconstruct(choices)
return dp[n], path
Common Pitfalls and How to Avoid Them
- Infinite recursion in memoization — cyclic dependencies cause infinite loops. Solution: ensure the recurrence strictly reduces problem size.
- Wrong fill order in tabulation — using
dp[i+1]before it is computed. Solution: map dependencies and compute in topological order. - Forgetting base cases — missing base cases or wrong initialization. Solution: test with smallest inputs first.
- Cache invalidation — not clearing memo between test cases. Solution: create a new memo for each call or use timestamps.
- Memory blowup in 2D DP — O(m×n) table for large inputs. Solution: use rolling arrays or optimize to O(min(m, n)).
- Premature optimization — trying to optimize space before correctness. Solution: first get a correct solution, then optimize.
Practice Problems with Solutions
Beginner Problems
- Climbing Stairs (LeetCode 70)
- Memoization: easy recursive + cache
- Tabulation: O(n) array
- Optimized: O(1) space with two variables
- Min Cost Climbing Stairs (LeetCode 746)
- Memoization: natural recursion
- Tabulation: O(n) array
- Optimized: O(1) space
Intermediate Problems
- House Robber (LeetCode 198)
- Memoization: simple state definition
- Tabulation: O(n) array
- Optimized: O(1) space
- Decode Ways (LeetCode 91)
- Memoization: intuitive recursion
- Tabulation: O(n) array
- Optimized: O(1) space
- Unique Paths (LeetCode 62)
- Memoization: 2D recursion
- Tabulation: 2D table
- Optimized: O(n) row compression
Advanced Problems
- Coin Change (LeetCode 322)
- Memoization: recursive with target state
- Tabulation: O(amount) array
- Both work equally well
- Edit Distance (LeetCode 72)
- Memoization: 2D recursion
- Tabulation: 2D table
- Optimized: O(n) space with rolling array
Interview Tips for Each Approach
Memoization Interview Tips
- Start recursive — natural to explain
- Add cache — mention memoization explicitly
- Handle base cases first — always check termination
- Explain overhead — mention recursion stack
- Discuss trade-offs — time vs space
- Be ready to convert — interviewers may ask for an iterative version
Tabulation Interview Tips
- Draw the table — visualize on whiteboard
- Explain fill order — show dependency graph
- Start from base — explain initialization
- Optimize space — mention rolling arrays if applicable
- Trace example — walk through small input
- Discuss alternatives — why tabulation over memoization?
Common Interview Follow-ups
- "Can you implement the iterative version?"
- "How would you optimize space?"
- "What's the time and space complexity?"
- "Can you identify the overlapping subproblems?"
- "How would you handle large inputs?"
Quick Reference Card
Memoization vs Tabulation Quick Reference
| Aspect | Memoization | Tabulation |
|---|---|---|
| Style | Top-down | Bottom-up |
| Implementation | Recursion + cache | Iteration + array |
| Pros | Natural, sparse states | No recursion, optimizable |
| Cons | Stack risk, overhead | Fill order needed |
| Space | O(visited) + stack | O(all states) |
| Best for | Complex recurrences | Simple iteration |
| Debug | Harder | Easier |
| Interview | Quick draft | Final solution |
When to Use Which
Use Memoization when
- Natural recursive formulation
- Sparse state space
- Quick interview solution
- Clear base cases
Use Tabulation when
- Large input size
- Need space optimization
- Simple state dependencies
- Production code
Space Optimization Techniques
- 1D DP → O(1) with variables
- 2D DP → O(n) with rolling array
- Multiple dimensions → keep only necessary rows
- Sparse DP → use dictionary/hash map
- Path reconstruction → store choices separately
Real-Life Applications
In production systems, the memoization vs tabulation choice mirrors how software computes on demand versus precomputes and stores results.
| Domain | Real scenario | Approach | Why |
|---|---|---|---|
| Web APIs | HTTP response caching (Redis, CDN edge cache) | Memoization | Compute and store only when a URL/key is requested; skip unused paths |
| Frontend (React) | useMemo, React.memo, selector memoization | Memoization | Recompute derived UI state only when inputs change |
| Spreadsheets | Excel / Google Sheets formula dependency graph | Both | On-edit recalc ≈ memoized lazy eval; full workbook rebuild ≈ tabulation |
| Compilers | Lookup tables for keywords, opcodes, jump targets | Tabulation | All entries filled once at compile/link time for O(1) access |
| Games | Chess opening books, minimax with transposition tables | Memoization | Cache board positions visited during search; sparse tree |
| Maps / routing | Precomputed shortest-path tables between hubs | Tabulation | All-pairs or hub tables built offline; queries are O(1) lookup |
| Machine learning | Dynamic programming in HMM forward-backward, Viterbi | Tabulation | Fill probability lattice in fixed order over time steps |
| Databases | Materialized views vs on-the-fly query cache | Tabulation / memo | Materialized = eager precompute; query cache = lazy per request |
Key Takeaway
Memoization and tabulation are two views of the same DP table—lazy (on demand) vs eager (fill all needed cells in order). Pick based on clarity, constraints, and space—not because one is “more correct.”
Next up: DP properties & complexity · Fibonacci & climbing stairs · Introduction to DP