DSA Tutorial
🔍

Memoization vs Tabulation

The Core Difference

Both memoization and tabulation are implementations of the same DP algorithm. They compute the same values using the same recurrence and produce identical answers. The difference is when and how each state is computed.

MEMOIZATION (top-down):           TABULATION (bottom-up):
  Start from the question.          Start from the answers.
  Recurse lazily toward base.       Fill eagerly from base up.
  Compute only needed states.       Compute ALL states in range.
  Cache results for reuse.          Table filled sequentially.

MENTAL MODEL:
  Memoization: "I need fib(10).     Tabulation: "I'll compute
    Let me check if I've done         fib(0), fib(1), ..., fib(10)
    this. No? Then I'll compute       in order. When I need fib(10),
    it and remember."                 it's already in the table."

BOTH ACHIEVE:
  O(unique sub-problems) time
  O(unique sub-problems) space (before space optimisation)

Head-to-Head Comparison

DIMENSION              MEMOIZATION          TABULATION
──────────────────────────────────────────────────────────────────
Execution direction    Top-down             Bottom-up
States computed        Only reachable        All in range
Code structure         Recursive             Iterative loops
Base cases             Inside function       Pre-loop initialisation
Cache structure        HashMap or array      Array (always)
Recursion overhead     Yes (per call)        None
Stack overflow risk    Yes (deep recursion)  None
Cache locality         Poor (HashMap)        Excellent (sequential)
Space optimisation     Hard                 Easy (rolling arrays)
Ease of first draft    Easier               Harder (fill order)
Performance (large n)  Slower in practice   Faster in practice
Handles sparse states  Yes (skips them)      No (fills all)
Typical use case       Complex DAGs,         Well-defined ranges,
                       interval DP,          1D/2D sequences,
                       sparse problems       dense sub-problems

Scenario 1: Dense Sub-Problems — Tabulation Wins

When most or all states in the table are reached, tabulation's sequential access dominates.

EXAMPLE: Fibonacci for large n

n = 1,000,000

MEMOIZATION:
  1,000,001 function calls
  Each call: push frame, check dict, hash key, pop frame
  Python dict lookup: ~50ns per call
  Total: ~50ms + recursion stack overflow risk

TABULATION:
  Single loop: 1,000,000 iterations
  Each iteration: dp[i] = dp[i-1] + dp[i-2] (2 adds, 2 reads)
  Array access: ~1ns per iteration (cache-hot)
  Total: ~1ms, no stack risk

RATIO: ~50× slower for memoization, same O(n) asymptotic complexity.

STANDARD DENSE DP PROBLEMS WHERE TABULATION IS PREFERRED:
  Fibonacci, Climbing Stairs, House Robber
  Unique Paths (m×n grid — all cells reached)
  Longest Common Subsequence (entire m×n table filled)
  Minimum Path Sum
  Edit Distance

Scenario 2: Sparse Sub-Problems — Memoization Wins

When only a fraction of all possible states are actually needed, memoization avoids wasteful computation.

EXAMPLE: Top-Down DAG DP with sparse reachable states

Problem: You have items indexed 1..1000, weights 1..1000.
For knapsack, we compute dp[i][w] for i in [1..1000], w in [1..1000].
That's 1,000,000 states total.

BUT: if the items have large weights (minimum weight = 900),
only ~11 weights per item are valid (900, 901, ..., 1000).
Reachable states: 1000 × 11 = 11,000 out of 1,000,000.

TABULATION:  Fills all 1,000,000 cells. 989,000 are wasted work.
MEMOIZATION: Fills only 11,000 reachable cells. 89× less work.

OTHER SPARSE EXAMPLES:
  Interval DP with skip conditions
  Word break where only some prefix lengths are valid
  Game theory DP where only certain game states are reachable
  3D or higher-dimensional DP where most states are unreachable

Scenario 3: Deep Recursion — Tabulation Avoids Stack Overflow

PROBLEM: Memoized fib(100000) in Python

Default recursion limit: 1000 frames.
fib(100000) requires 100000 nested calls → CRASH.

sys.setrecursionlimit(200000) is a workaround but:
  - Wastes memory for 200000 stack frames
  - Python stack frame: ~300 bytes → 60MB just for stack
  - C stack has a hard OS limit (typically 8MB on Linux)
  - Still crashes for n > ~30000 with default 8MB stack

TABULATION:
  for i in range(2, 100001):
      dp[i] = dp[i-1] + dp[i-2]
  No recursion. O(1) stack. Works for any n.

LANGUAGES MOST AFFECTED:
  Python:  Recursion limit 1000 (easily hit for n > 1000)
  Java:    Stack size configurable (-Xss), defaults ~512KB-1MB
  C++:     OS stack limit (~1-8MB), about 20000-100000 frames
  JavaScript: Typically 10000-15000 calls before stack overflow

RULE: If n > 10000, prefer tabulation to avoid stack issues.
  If n > 1000 in Python, prefer tabulation.

Side-by-Side Code: Same Problem, Both Approaches

1import java.util.*; 2 3public class MemoVsTabulation { 4 5 // ════════════════════════════════════════════════════════ 6 // PROBLEM: House Robber 7 // dp[i] = max money robbing houses 0..i 8 // dp[i] = max(dp[i-1], dp[i-2] + nums[i]) 9 // ════════════════════════════════════════════════════════ 10 11 // ── MEMOIZATION (top-down) ──────────────────────────── 12 static int[] nums; 13 static int[] memo; 14 15 static int robMemo(int i) { 16 if (i < 0) return 0; // ① Base: no houses 17 if (i == 0) return nums[0]; // ① Base: one house 18 if (memo[i] != -1) return memo[i]; // ② Cache check 19 memo[i] = Math.max( 20 robMemo(i - 1), // Skip house i 21 robMemo(i - 2) + nums[i] // Rob house i 22 ); 23 return memo[i]; 24 } 25 26 static int houseRobberMemo(int[] n) { 27 nums = n; 28 memo = new int[n.length]; 29 Arrays.fill(memo, -1); 30 return robMemo(n.length - 1); 31 } 32 33 // ── TABULATION (bottom-up) ──────────────────────────── 34 static int houseRobberTab(int[] nums) { 35 int n = nums.length; 36 if (n == 1) return nums[0]; 37 int[] dp = new int[n]; 38 dp[0] = nums[0]; // ② Base case 0 39 dp[1] = Math.max(nums[0], nums[1]); // ② Base case 1 40 for (int i = 2; i < n; i++) { // ③ Fill left to right 41 dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]); 42 } 43 return dp[n-1]; 44 } 45 46 // ── TABULATION space-optimised O(1) ────────────────── 47 static int houseRobberOpt(int[] nums) { 48 int n = nums.length; 49 if (n == 1) return nums[0]; 50 int prev2 = nums[0], prev1 = Math.max(nums[0], nums[1]); 51 for (int i = 2; i < n; i++) { 52 int curr = Math.max(prev1, prev2 + nums[i]); 53 prev2 = prev1; prev1 = curr; 54 } 55 return prev1; 56 } 57 58 // ════════════════════════════════════════════════════════ 59 // PROBLEM: LCS — where memoization structure is natural 60 // dp[i][j] = LCS of s1[0..i-1] and s2[0..j-1] 61 // ════════════════════════════════════════════════════════ 62 63 static int lcsMemo(String s1, String s2, int i, int j, int[][] m) { 64 if (i == 0 || j == 0) return 0; 65 if (m[i][j] != -1) return m[i][j]; 66 if (s1.charAt(i-1) == s2.charAt(j-1)) 67 m[i][j] = 1 + lcsMemo(s1, s2, i-1, j-1, m); 68 else 69 m[i][j] = Math.max(lcsMemo(s1,s2,i-1,j,m), lcsMemo(s1,s2,i,j-1,m)); 70 return m[i][j]; 71 } 72 73 static int lcsTab(String s1, String s2) { 74 int[][] dp = new int[s1.length()+1][s2.length()+1]; 75 for (int i=1;i<=s1.length();i++) 76 for (int j=1;j<=s2.length();j++) 77 dp[i][j] = s1.charAt(i-1)==s2.charAt(j-1) 78 ? 1+dp[i-1][j-1] : Math.max(dp[i-1][j],dp[i][j-1]); 79 return dp[s1.length()][s2.length()]; 80 } 81 82 public static void main(String[] args) { 83 int[] h = {2, 7, 9, 3, 1}; 84 85 System.out.println("House Robber:"); 86 System.out.println(" Memoized: " + houseRobberMemo(h)); // 12 87 System.out.println(" Tabulated: " + houseRobberTab(h)); // 12 88 System.out.println(" Optimised: " + houseRobberOpt(h)); // 12 89 90 String s1 = "abcde", s2 = "ace"; 91 int[][] lm = new int[s1.length()+1][s2.length()+1]; 92 for (int[] r : lm) Arrays.fill(r, -1); 93 94 System.out.println("LCS:"); 95 System.out.println(" Memoized: " + 96 lcsMemo(s1, s2, s1.length(), s2.length(), lm)); // 3 97 System.out.println(" Tabulated: " + lcsTab(s1, s2)); // 3 98 } 99}
Output:
House Robber:
  Memoized:   12
  Tabulated:  12
  Optimised:  12
LCS:
  Memoized:   3
  Tabulated:  3
Word Break (memo): true
Word Break (tab):  true

When Memoization Is Easier to Write

Some problems have complex state transitions that are easier to express recursively.

PROBLEMS WHERE MEMOIZATION SHINES STRUCTURALLY:

1. INTERVAL DP — "Burst Balloons", "Matrix Chain Multiplication"
   try_all_k: dp[i][j] = max over k in [i,j]: cost(i,k,j) + dp[i][k] + dp[k][j]

   Memoization: write the recursion naturally.
   Tabulation: must fill by interval length (non-obvious loop order).

2. WORD BREAK / STRING SEGMENTATION
   "Can s[start..] be segmented?"
   Memoization: dp(start) tries all splits naturally.
   Tabulation: dp[i] iterating backwards over j is less intuitive.

3. TREE/GRAPH DP — "House Robber III (binary tree)"
   Recursion IS the natural traversal.
   Memoization on tree node directly — tabulation would need to flatten the tree.

4. GAME THEORY DP — Minimax, Stone Game
   Recursive win/lose logic is clearest in recursive form.
   Tabulation requires carefully tracking whose turn it is.

PRACTICAL APPROACH:
  Step 1: Write the memoized solution (faster to implement).
  Step 2: If performance is insufficient, convert to tabulation.
  Step 3: If space is the bottleneck, apply rolling array to tabulation.

Choosing the Right Approach

USE MEMOIZATION WHEN:
  ✓ Problem structure is naturally recursive (trees, graphs, intervals)
  ✓ Sub-problem space is SPARSE — only a small fraction of states reachable
  ✓ You need to write correct code quickly (interview, prototyping)
  ✓ Fill order for tabulation is non-obvious (interval DP, game DP)
  ✓ States have non-integer or complex keys (use HashMap naturally)
  ✓ n is small (< 1000 in Python, < 10000 in Java/C++)

USE TABULATION WHEN:
  ✓ Sub-problem space is DENSE — most states in the range are needed
  ✓ n is large (> 10000 in any language, > 1000 in Python)
  ✓ Space optimisation (rolling arrays) is needed or desired
  ✓ You need predictable performance (no recursion overhead)
  ✓ The fill order is obvious (1D, standard 2D grid)
  ✓ Production code where recursion limit is a concern

EITHER WORKS (choose by comfort):
  ✓ Standard 1D DP (Fibonacci, Climbing Stairs, House Robber)
  ✓ Standard 2D DP (LCS, Edit Distance, Unique Paths)
  ✓ Knapsack variants

DECISION FLOWCHART:
  Is the recursive structure very clear?
    YES → Start with memoization
    NO  → Go directly to tabulation

  Are sub-problems sparse (most states unreachable)?
    YES → Keep memoization
    NO  → Consider tabulation

  Is n > 1000 in Python or > 100000 in Java/C++?
    YES → Use tabulation (avoid stack overflow)
    NO  → Either works

  Is space optimisation needed?
    YES → Use tabulation (rolling arrays much easier)
    NO  → Either works

Practical Conversion: When to Switch

REAL WORKFLOW:

1. PROTOTYPE with memoization
   - Write the recursion: natural, matches the problem statement
   - Add cache: two lines
   - Verify correctness on examples
   
2. PROFILE if needed
   - Is it too slow? → Convert to tabulation
   - Is stack overflowing? → Convert to tabulation
   - Is memory too high? → Add space optimisation to tabulation
   
3. OPTIMISE with tabulation
   - Convert recursive calls to table lookups
   - Remove cache check (loop order handles freshness)
   - Apply rolling array if space is critical

THE CONVERSION IS MECHANICAL:
  fib(n-1)        →  dp[i-1]
  fib(n-2)        →  dp[i-2]
  if base_case    →  explicit dp[0]=, dp[1]=
  memo[n] = val   →  dp[i] = val (in loop body)
  return memo[n]  →  return dp[n] (after loop)

EXAMPLE: House Robber memoized → tabulated

MEMOIZED:                         TABULATED:
  memo = {}                         dp = [0]*n
  def rob(i):                       dp[0] = nums[0]
    if i<0: return 0                dp[1] = max(nums[0],nums[1])
    if i==0: return nums[0]         for i in range(2,n):
    if i in memo: return memo[i]        dp[i] = max(dp[i-1],
    memo[i] = max(rob(i-1),                       dp[i-2]+nums[i])
                  rob(i-2)+nums[i])  return dp[n-1]
    return memo[i]
  return rob(n-1)

Performance Summary

CONSTANT FACTOR COMPARISON (same asymptotic complexity):

Operation          Memoization                   Tabulation
───────────────────────────────────────────────────────────────
Per-state cost     ~20-100 instructions          ~3-10 instructions
                   (function call + hash lookup  (array access + arithmetic)
                   + cache check + store)

Memory access      Random (HashMap, pointer      Sequential (array, row-major)
pattern            indirection)                  → cache-friendly

Stack memory       O(depth) frames               O(1) (loop counter only)

Total factor       ~10-50× slower for dense      Baseline fast
(empirical)        sub-problems

FOR CORRECTNESS:   Identical — same answer guaranteed
FOR INTERVIEWS:    Memoization is faster to write; tabulation may be faster to run
FOR PRODUCTION:    Tabulation preferred for performance-critical paths

Summary

Memoization and tabulation implement the same DP algorithm from opposite directions. They always produce identical answers. The choice is about implementation convenience and runtime efficiency.

Memoization (top-down):

  • Write the recursion naturally, add two lines (cache check + store)
  • Only computes reachable states (lazy)
  • Easier to write for complex recursive structures (interval DP, tree DP)
  • Risk: stack overflow for large n; slower due to function call overhead

Tabulation (bottom-up):

  • Fill a table iteratively in dependency order
  • Computes all states in range (eager)
  • Faster in practice: sequential memory, no recursion overhead
  • Easier to apply space optimisation (rolling arrays)

The decision:

SituationChoose
Quick prototype / interview draftMemoization
Large n, production codeTabulation
Sparse sub-problemsMemoization
Dense sub-problemsTabulation
Space optimisation neededTabulation
Tree / graph / interval DPMemoization
Deep recursion risk (Python n>1000)Tabulation

The practical workflow: prototype with memoization → verify correctness → convert to tabulation if performance or stack depth demands it.

In the next topic you will explore 1D Dynamic Programming — solving problems where the state is a single index, including classic patterns like House Robber, Jump Game, Decode Ways, and Word Break.

Suggested Quiz

A DP problem has 1000×1000 states but only 50 are actually reached during computation. Which approach wastes less time?

1/6