🚀 Phases 1–5 are live — Days 1–17 cover the foundations and the algorithmic patterns. See the roadmap →
Day 14 - Dynamic ProgrammingMemoization vs Tabulation

Memoization vs Tabulation

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.

AspectMemoization (top-down)Tabulation (bottom-up)
StyleRecursion + cacheIterative loop filling a table
DirectionStart at the answer, recurse downStart at the base case, build up
VisitsOnly the subproblems you needEvery subproblem in the table
StackUses recursion stack (O(state depth))No recursion stack
Easier to write?Yes — mirrors the recurrenceNeeds you to pick the fill order
Faster constant?Slightly slower (function calls)Slightly faster (tight loops)
Space-optimizable?HardEasy — 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.

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 to O(m) or O(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:

StepMemoizationTabulation
Write base caseif (...) return ... at top of functiondp[base index] = ... before loop
Write transitionreturn f(smaller) + f(other smaller)dp[i] = dp[i - 1] + dp[i - 2]
Cacheif memo[i] is set, return memo[i](implicit — array is the cache)
Call sitereturn f(n)return dp[n]
DirectionTop-down: needed cells firstBottom-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're solving a knapsack-style problem with state (item_index, capacity_remaining). You realize many capacities never get visited because items are large. Which style is more efficient?
What's the main reason people convert their working memoized solution to tabulation?
Which is true about the recursion-stack space of memoization?

Next: the classic patterns — seven shapes that show up in nearly every DP interview problem.