Memoization vs Tabulation
The same DP can be written two completely different ways — recurse-and-cache, or loop-and-fill — and they give identical answers with identical big-O. So why learn both? Because one is far easier to write (start here), and the other unlocks something the first never can: collapsing an
O(n)table down toO(1)memory. Knowing which to reach for, and how to convert between them, is the difference between “accepted” and “memory limit exceeded.”
There are two ways to write any DP solution. They produce the same answers and have the same big-O time complexity. They differ in how natural the code feels, how much memory they use, and how easy they are to optimize further.
| Aspect | Memoization (top-down) | Tabulation (bottom-up) |
|---|---|---|
| Style | Recursion + cache | Iterative loop filling a table |
| Direction | Start at the answer, recurse down | Start at the base case, build up |
| Visits | Only the subproblems you need | Every subproblem in the table |
| Stack | Uses recursion stack (O(state depth)) | No recursion stack |
| Easier to write? | Yes — mirrors the recurrence | Needs you to pick the fill order |
| Faster constant? | Slightly slower (function calls) | Slightly faster (tight loops) |
| Space-optimizable? | Hard | Easy — drop the table to a row or two |
If you’re stuck, start with memoization. Once it works, consider converting to tabulation for speed or to unlock space optimization. Don’t convert before it works — it’s a recipe for double bugs.
The same problem, three ways
Let’s use the same problem — climbing stairs — and write it as plain recursion, top-down DP, and bottom-up DP, side by side.
Style 1 — pure recursion (the brute force)
Correct. Time: O(2^n). Space: O(n) recursion depth. Times out on the LeetCode judge around n = 35.
Style 2 — memoization (top-down DP)
Add a cache. Check the cache before recursing; fill the cache before returning.
Time: O(n). Space: O(n) for the cache + O(n) recursion stack.
Notice how the body of the function is identical to the brute force — only the cache check and write are new. This is the memoization template: take any recursion, sandwich the body between a cache hit-check and a cache write-back. Done.
Style 3 — tabulation (bottom-up DP)
Replace recursion with a loop that fills the table in order.
Time: O(n). Space: O(n). No recursion, no stack.
And because each cell only depends on the previous two, we can collapse the entire table to two variables:
Time: O(n). Space: O(1). This is the space-optimized bottom-up form — and it’s why tabulation is worth converting to for problems with very tight rolling dependencies.
Recall the one line that rolls the window forward (no array, just two values):
How to convert memoization to tabulation
The mechanical recipe:
Identify the state variables and their ranges
If your memo is keyed on (i, j) with 0 <= i <= n and 0 <= j <= m, your table is (n + 1) x (m + 1).
Identify the dependency direction
Look at what f(i, j) recursively calls. If it calls f(i - 1, j) and f(i, j - 1), then computing row i needs row i - 1 already done. So you fill the table in increasing i, increasing j order. Always: the cell you’re computing must depend only on cells already filled.
Translate base cases into table initialization
if (i == 0) return ... in the recursion becomes dp[0][...] = ... in the table, set before the loops.
Translate the recursive body into a loop body
Replace every f(a, b) recursive call with dp[a][b]. Replace the return with an assignment to dp[i][j].
Return dp[final state]
Whatever state you would have started the recursion with — dp[n], dp[n - 1][m - 1], etc.
Once you’ve done this conversion three or four times, you can do it in your head while reading the problem.
When to prefer each style
Prefer memoization when
- The state space is large but sparse. If most subproblems are never visited, tabulation wastes time filling the irrelevant cells. Memoization only computes what it needs.
- The transitions are complex. Multi-way recursion (try every split, every subset, every partition) is much easier to express as code that recurses than as a multi-loop table fill.
- You’re still figuring the problem out. Get the recursion right, slap a cache on, ship it. Optimize later if needed.
Prefer tabulation when
- You want to space-optimize. Tabulation lets you keep only the rows you still need (one or two at a time), often dropping
O(n × m)memory down toO(m)orO(1). Memoization can’t do this — the cache has to hold every visited cell. - You’re running into stack overflow. Deep recursion (state depth
>~10,000 in most languages) blows the stack. Tabulation has no stack. - You want the absolute fastest constant factor. Tight inner loops with no function-call overhead matter on heavily-judged problems and competitive-programming benchmarks.
A side-by-side cheat sheet
For the same DP problem, here’s how the two styles compare on every axis:
| Step | Memoization | Tabulation |
|---|---|---|
| Write base case | if (...) return ... at top of function | dp[base index] = ... before loop |
| Write transition | return f(smaller) + f(other smaller) | dp[i] = dp[i - 1] + dp[i - 2] |
| Cache | if memo[i] is set, return memo[i] | (implicit — array is the cache) |
| Call site | return f(n) | return dp[n] |
| Direction | Top-down: needed cells first | Bottom-up: every cell, in order |
A subtle gotcha: the base case in memo arrays
When you use -1 (or any sentinel value) to mean “not yet computed,” the sentinel must be a value the function can never legitimately return. If your DP computes counts that can be -1 (some Manhattan-distance puzzle, or a problem with “no solution → -1” outputs), pick a different sentinel — or use a parallel boolean computed[i] array, or use language-native nullable types (Integer in Java, Optional<int> in C++17, None in Python).
This is the kind of bug that doesn’t crash — it just silently returns wrong answers. Watch for it.
Quick check
You can now write any DP both ways and convert between them. Next: the classic patterns — the handful of recurring DP shapes (0/1 knapsack, LIS, grid paths, interval, …) so you recognize which table to fill the moment you read the problem.