Fibonacci: Memo vs Tabulation

Dynamic Programming
avg O(n)

Fibonacci is the "hello world" of dynamic programming, because it shows the whole idea in one picture. The naive recursive version recomputes the same numbers over and over - fib(50) that way makes about 40 billion calls. Remember each answer the first time you compute it, and the same problem needs 49 additions. That is dynamic programming: overlapping subproblems + remembering = exponential becomes linear.

Watch the table fill

Press play - or drag the timeline and step through it yourself.
Entry 1Additions 0Step 1 / 26
0
0
0
0
0
0
0
0
0
0
0
0
0

Goal: fib(12). Naive recursion would make hundreds of repeated calls. Instead: fill a table once, left to right.

reading the two before writing computed

Complexity

Best
O(n)
Input already in order
Average
O(n)
Normal mixed input
Worst
O(n)
Worst possible input
Space
O(n)
Extra memory used

Counting stairs with a notebook

Ask someone: "How many ways can you climb 40 stairs taking 1 or 2 steps at a time?" The pattern is Fibonacci: ways(40) = ways(39) + ways(38). Solving it by pure recursion is like asking a friend, who asks two friends, who each ask two friends - and thousands of people end up solving the *same* small staircases again and again. The notebook version: one person starts at stair 1, writes each answer down, and looks answers up instead of re-asking. Same math, 40 lines of notebook instead of a phone tree of millions.

How it works, step by step

  1. Notice the recursive rule: fib(n) = fib(n−1) + fib(n−2), with fib(0) = 0 and fib(1) = 1.

  2. Notice the problem with it: fib(n−1) and fib(n−2) both need fib(n−3) - the same subproblems appear again and again (overlapping subproblems).

  3. Fix #1 - memoization (top-down): keep the recursion, but store every result in a map the first time. Every later call is a lookup.

  4. Fix #2 - tabulation (bottom-up): drop the recursion. Fill an array from fib(0) upward; each entry is one addition of two earlier entries.

  5. Both do O(n) work instead of O(2ⁿ). Tabulation also shows the last trick: you only ever read the last two entries, so two variables replace the whole table - O(1) space.

  6. This exact recipe - find the rule, spot the overlap, remember or tabulate, then shrink the storage - is how every DP problem in this collection is solved.

The code, in JavaScript

The version to never ship: mathematically correct, exponentially slow. fib(40) takes seconds; fib(50) takes hours. The tree of calls doubles at every level.

javascript
function fib(n) {
  if (n <= 1) return n;
  return fib(n - 1) + fib(n - 2);
}

fib(10);   // 177 calls      - fine
fib(30);   // 2.7 million    - noticeable pause
fib(45);   // 3.6 billion    - go make coffee

// Why: fib(5) calls fib(4) and fib(3).
// fib(4) calls fib(3) AGAIN. Everything below repeats.

Dry run: building fib(0..12) bottom-up

The same steps the visualizer plays, written as a table. Generated by running the real algorithm - not written by hand.

StepEntryArrayWhat happened
11[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]Base case: fib(0) = 0.
21[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]Base case: fib(1) = 1.
31[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]fib(2) = fib(1) + fib(0) = 1 + 0 - both already in the table, no recursion needed.
41[0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]Write fib(2) = 1.
52[0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]fib(3) = fib(2) + fib(1) = 1 + 1 - both already in the table, no recursion needed.
62[0, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0]Write fib(3) = 2.
73[0, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0]fib(4) = fib(3) + fib(2) = 2 + 1 - both already in the table, no recursion needed.
83[0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0]Write fib(4) = 3.
94[0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0]fib(5) = fib(4) + fib(3) = 3 + 2 - both already in the table, no recursion needed.
104[0, 1, 1, 2, 3, 5, 0, 0, 0, 0, 0, 0, 0]Write fib(5) = 5.
115[0, 1, 1, 2, 3, 5, 0, 0, 0, 0, 0, 0, 0]fib(6) = fib(5) + fib(4) = 5 + 3 - both already in the table, no recursion needed.
125[0, 1, 1, 2, 3, 5, 8, 0, 0, 0, 0, 0, 0]Write fib(6) = 8.
136[0, 1, 1, 2, 3, 5, 8, 0, 0, 0, 0, 0, 0]fib(7) = fib(6) + fib(5) = 8 + 5 - both already in the table, no recursion needed.
146[0, 1, 1, 2, 3, 5, 8, 13, 0, 0, 0, 0, 0]Write fib(7) = 13.
157[0, 1, 1, 2, 3, 5, 8, 13, 0, 0, 0, 0, 0]fib(8) = fib(7) + fib(6) = 13 + 8 - both already in the table, no recursion needed.
167[0, 1, 1, 2, 3, 5, 8, 13, 21, 0, 0, 0, 0]Write fib(8) = 21.
178[0, 1, 1, 2, 3, 5, 8, 13, 21, 0, 0, 0, 0]fib(9) = fib(8) + fib(7) = 21 + 13 - both already in the table, no recursion needed.
188[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 0, 0, 0]Write fib(9) = 34.
199[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 0, 0, 0]fib(10) = fib(9) + fib(8) = 34 + 21 - both already in the table, no recursion needed.
209[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 0, 0]Write fib(10) = 55.
2110[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 0, 0]fib(11) = fib(10) + fib(9) = 55 + 34 - both already in the table, no recursion needed.
2210[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 0]Write fib(11) = 89.
2311[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 0]fib(12) = fib(11) + fib(10) = 89 + 55 - both already in the table, no recursion needed.
2411[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]Write fib(12) = 144.

Good choice when…

  • A recursive solution exists but recomputes the same subproblems - memoize it and keep your recursive thinking.
  • The dependency order is simple (each entry needs only earlier ones) - tabulate and skip recursion overhead entirely.
  • You need every intermediate answer anyway (all of fib(0..n), all shortest prefixes...) - the table *is* the output.
  • As the template: nearly every DP problem - knapsack, LCS, coin change - is this exact pattern with a bigger table.

Bad choice when…

  • The subproblems never repeat (like merge sort's halves) - a cache stores everything and hits nothing; that is divide and conquer, not DP.
  • A closed formula or simple loop already exists - Fibonacci itself has both; DP on it is a teaching device, not a necessity.
  • The state space is astronomically large and unstructured - a cache the size of the universe helps nobody; look for greedy structure or approximation instead.

Common mistakes

  • Memoization and tabulation compute the same thing in opposite directions: memo starts at the goal and recurses down; tabulation starts at the base and builds up. Interviews love asking for the conversion - practice it on this problem, where both fit on one slide.
  • Recursion depth is real: memoized fib(20000) overflows JavaScript's call stack even though the math is fine. Tabulation has no such limit - one reason to learn the bottom-up form.
  • fib(79) already exceeds Number.MAX_SAFE_INTEGER - the answers silently lose precision long before the algorithm slows down. Use BigInt when n is large.
  • The space optimization (two variables) works because fib(i) reads only i−1 and i−2. In 2-D problems the same trick keeps one row instead of the matrix - but only when the reading pattern allows it. Check what each cell reads before shrinking.

Fibonacci: Memo vs Tabulation vs. its closest relatives

AlgorithmBestAverageWorstSpaceStable
Fibonacci: Memo vs Tabulationthis pageO(n)O(n)O(n)O(n)-
Coin ChangeO(n·a)O(n·a)O(n·a)O(a)-
0/1 KnapsackO(n·W)O(n·W)O(n·W)O(W)-
Kadane's AlgorithmO(n)O(n)O(n)O(1)-