Memoization vs Tabulation
Table of Contents + β
So you wrote a clean recursive solution. It works fine on small inputs. Then you give it a bigger number and it just hangs there forever. The reason is that your recursion keeps solving the same small piece again and again. Dynamic programming fixes this. It solves each small piece once and then remembers the answer. There are two ways to do that remembering. One is memoization and the other is tabulation. Let us learn both.
π What Are These Two Things?
Both memoization and tabulation are styles of dynamic programming. Dynamic programming means breaking a big problem into smaller repeating pieces. You store each pieceβs answer so you never compute it twice.
The real difference is the direction you go.
- Memoization is top-down. You write the natural recursion, the one you would write anyway. Then you add a cache. Before you compute an answer, you check the cache. If it is there, you return it right away. If not, you compute it once and save it.
- Tabulation is bottom-up. You skip recursion completely. You start from the smallest pieces and fill a table from the bottom. Then you build up to the final answer step by step.
So it is the same idea seen from two directions. Memoization starts at the top problem and works down to the base. Tabulation starts at the base and works up to the top.
π³ A Quick Analogy
Think about a cook learning recipes.
Memoization is like a cook who tries to make a big dish, realizes they need a sauce, makes the sauce, and writes it on a card so they never make that sauce from scratch again. They start from the final dish and work backward to whatever they need.
Tabulation is like a cook who first preps every basic ingredient and sauce in order, lines them up on the counter, and only then assembles the final dish. They start from the basics and build up.
Both cooks avoid repeating work. They just organize it differently.
π― The Shared Example: Fibonacci
We will use Fibonacci for both approaches. Fibonacci is a sequence where each number is the sum of the two before it: 0, 1, 1, 2, 3, 5, 8, 13, and so on.
The plain recursion looks like fib(n) = fib(n-1) + fib(n-2). The trouble is that plain recursion recomputes the same values over and over. To get fib(5) it computes fib(3) twice and fib(2) three times. It gets much worse as n grows.
Here is how the calls explode for a small n. Notice how fib(2) shows up more than once.
Both memoization and tabulation kill this repeated work. Each fib value gets computed exactly once.
πΌ Approach 1: Memoization (Top-Down)
The idea here is simple. Keep the recursion you already know. Then add a cache that maps n to its answer. This style is called top-down because you start at the big problem and recurse down.
Steps to do memoization:
- Make a cache (an array or a map) and mark every slot as empty.
- Write the recursive function as normal, with the base cases first.
- At the start of the function, check the cache. If the answer for
nis already there, return it right away. - Otherwise compute the answer with the usual recursion.
- Save that answer in the cache before you return it.
Here is Fibonacci with memoization in all five languages.
# top-down fibonacci with a cachedef fib(n, cache): if n < 2: # base cases: fib(0)=0, fib(1)=1 return n if cache[n] != -1: # already computed? return cache[n] # return the saved answer cache[n] = fib(n - 1, cache) + fib(n - 2, cache) # compute once, then save return cache[n]
memo = [-1] * 100 # -1 means emptyprint("fib(10) =", fib(10, memo))The output of the above code will be:
fib(10) = 55Note
Time complexity is O(n) and space complexity is O(n). Each value from 0 to n is computed exactly once and then stored, so the work grows in a straight line with n. The cache and the recursion call stack both take O(n) space.
π½ Approach 2: Tabulation (Bottom-Up)
Here we drop recursion. We make a table, fill the smallest answers first, then walk upward with a simple loop. This style is called bottom-up because you start at the smallest pieces and build toward the answer.
Steps to do tabulation:
- Make a table (an array) of size
n + 1. - Fill in the base cases by hand:
table[0] = 0andtable[1] = 1. - Loop from the smallest unknown index up to
n. - At each step set
table[i] = table[i-1] + table[i-2], using values you already filled. - When the loop ends,
table[n]holds the final answer.
Here is Fibonacci with tabulation in all five languages.
# bottom-up fibonacci using a tabledef fib(n): if n < 2: # base cases return n table = [0] * (n + 1) table[0] = 0 # smallest answers filled by hand table[1] = 1 for i in range(2, n + 1): # build upward table[i] = table[i - 1] + table[i - 2] return table[n] # final answer sits at the top
print("fib(10) =", fib(10))The output of the above code will be:
fib(10) = 55Note
Time complexity is O(n) and space complexity is O(n). The loop runs once for each value up to n, and the table holds n + 1 entries. There is no recursion here, so there is no call stack to worry about.
βοΈ Which One Should You Pick?
Both give you the same answer with the same O(n) time. The choice is about how you like to think and what the problem needs.
Here is a side-by-side look.
| Point | Memoization (Top-Down) | Tabulation (Bottom-Up) |
|---|---|---|
| Direction | From the big problem down to the base | From the base up to the big problem |
| Uses recursion | Yes | No, just a loop |
| Easy to write from plain recursion | Very easy, just add a cache | Needs you to rethink the order |
| Call stack | Uses one, can overflow for large n | None, so no overflow risk |
| Speed in practice | Slower, function calls cost time | Often faster, plain loop |
| Computes only needed pieces | Yes for sparse problems; for fib it still touches every index | Fills the whole table |
A simple rule of thumb. If you already have a working recursion and just want it fast, reach for memoization. If n can be huge and you fear a stack overflow, or you want the fastest loop, reach for tabulation.
Tip
Tabulation can often shrink space too. For Fibonacci you only ever need the last two values, not the whole table. So you can keep two variables instead of an array and drop the space to O(1). That trick does not work as cleanly with memoization.
β οΈ Common Mistakes
Watch out for these slip-ups when you write either approach.
- Forgetting to initialize the cache to an βemptyβ marker. If your empty marker is 0, but a real answer can also be 0, you will get confused results. Use -1 or a separate βfilledβ flag.
- Returning before checking the cache in memoization. Always check the cache first, then compute. Otherwise you lose the whole benefit.
- Wrong table size in tabulation. The table must be size
n + 1so that indexnexists. An off-by-one here causes an out-of-bounds crash. - Filling the table in the wrong order. In tabulation you must fill smaller indices before the ones that depend on them. Loop upward, not downward.
- Overflow on large
n. Fibonacci grows fast. Use a 64-bit type likelong longorlong, or a big-integer type, whenngets large.
π§© What Youβve Learned
- β Dynamic programming solves each small subproblem once and stores the answer, so it never repeats work.
- β Memoization is top-down: keep your natural recursion and add a cache that returns saved answers right away.
- β Tabulation is bottom-up: skip recursion, fill a table from the smallest pieces, and build up to the final answer.
- β Both run in O(n) time for Fibonacci, but tabulation avoids the call stack and is often a bit faster.
- β Memoization is easiest when you already have a recursion; tabulation is safer for huge inputs and can sometimes drop space to O(1).
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What direction does memoization work in?
Why: Memoization keeps the natural recursion, which starts at the big problem and recurses down to the base cases.
- 2
What is the main thing tabulation avoids that memoization uses?
Why: Tabulation uses a plain loop instead of recursion, so there is no call stack and no risk of stack overflow.
- 3
For Fibonacci, what is the time complexity of both memoization and tabulation?
Why: Each value from 0 to n is computed exactly once, so the work grows linearly with n in both approaches.
- 4
Why should you initialize a memoization cache with a marker like -1 instead of 0?
Why: If 0 means 'empty' but a real subproblem answer is also 0, you cannot tell them apart, so use a value that is never a valid answer.