Tabulation (bottom-up DP)
Fill a table of answers with a loop, smallest questions first, so every answer you need is already there.
O(n)Space O(n)4 practice problemsStep-through pictureThe idea
Tabulation is dynamic programming with a loop and a table. You make an array where each cell answers one small question. You fill the cells in order, starting with the smallest questions, and each new cell is computed from cells that are already filled.
This is called bottom-up, because you start from the smallest cases at the bottom and work up to the big question. There is no recursion, so there is no risk of running out of call stack, and loops are usually a bit faster than function calls.
The hard part is choosing the order. Before you fill a cell, every cell it reads must already be filled. For a 1-D table that is usually left to right. For a 2-D table it is usually row by row.
An everyday example
Filling in a times table
When children fill in a times table on paper, they fill row after row, and each row helps with the next. The 7 times row is easy if you add 7 to the cell on its left. Nobody jumps to the bottom-right corner first. Every cell uses cells that are already written, and the last cell holds the answer you wanted.
| In the example | In the code |
|---|---|
| The paper grid | The best array |
| One box on the grid | best[i], the cheapest cost to stand on step i |
| Filling boxes in reading order | for (let i = 2; i <= n; i++) |
| Using boxes already written | best[i - 1] and best[i - 2] |
| The last box | return best[n] |
From the slow way to tabulation (bottom-up dp)
- Start from the recursive idea. The cheapest way to stand on step
iis the cheaper of two last jumps: from stepi - 1after payingcost[i - 1], or from stepi - 2after payingcost[i - 2]. Written as plain recursion, this asks the same questions again and again, and the number of calls roughly doubles with each extra step. - Memoization fixes the repeats: store each answer and look it up next time. But the first call is for the top step, and it has to recurse all the way down to step 0 before anything returns. For a staircase of 100,000 steps that is 100,000 nested calls, far past the roughly ten thousand that JavaScript engines allow.
- Look at which way the questions point. Step
ionly ever asks about stepsi - 1andi - 2, which are smaller. So the recursion is only finding its way down to step 0 so it can start answering upward. You already know where the bottom is, so you can start there yourself. - Make an array
bestwith one cell per step. Fill the cells you know without work, then walkiupward from 2. When you reach a cell, the two cells it reads are already filled, so each cell is two additions and a comparison. No call stack is used at all. That loop is tabulation.
How it works
- Decide what one cell means. Here
best[i]is the cheapest total cost to stand on stepi. The top of the stairs is stepn, just past the last step. - Create the table with one cell per state:
besthasn + 1cells. - Fill the base cases. You may start on step 0 or step 1 for free, so
best[0]andbest[1]are0. - Loop
ifrom 2 up ton, sobest[i - 1]andbest[i - 2]are always ready. - For each
i, compute both ways in: from one step below, payingcost[i - 1], or from two steps below, payingcost[i - 2]. Keep the smaller. - Return
best[n], the cell for the top.
Watch it run
This picture runs the code below on a real input. Press Step to move one step at a time, or Play to watch. The highlighted line is the one that just ran. Change the input to see other cases.
See every step at onceThe same run, laid out side by side
The code
Point at a note to light up the lines it explains.
function cheapestClimb(cost) {const n = cost.length;const best = new Array(n + 1).fill(0);for (let i = 2; i <= n; i++) {const fromOneBelow = best[i - 1] + cost[i - 1];const fromTwoBelow = best[i - 2] + cost[i - 2];best[i] = Math.min(fromOneBelow, fromTwoBelow);}return best[n];}
How fast it is
| Time | O(n) |
|---|---|
| Extra memory | O(n) |
One pass over n + 1 cells, each with two additions and a comparison. The table keeps one number per cell; keeping only the last two cells would make it O(1).
- Best case
O(1)costhas 0 or 1 steps. The loop starts ati = 2, which is already pastn, so nothing is filled andbest[n]is 0. - Average case
O(n)Any longer staircase: the loop fills each of the
n - 1cells from 2 tononce. - Worst case
O(n)The same as the average. The costs can't make a cell do more than two additions and a comparison.
Common mistakes
- Making the table one cell too short, so the top of the stairs (
best[n]) does not exist. Count the states carefully, including the final one. - Filling cells in an order where a cell reads something not yet computed. Check which cells the transition reads and loop so they come first.
- Starting the loop at 0 and reading
best[-1], which isundefinedin JavaScript and turns every sum intoNaN. Start after the base cases. - Leaving base cells at a default that does not match their meaning, such as
0where the cost of an impossible state should beInfinity.
When to use it
Look for these clues in a problem:
- You know the recurrence and every state depends on smaller states in a clear order.
- The input is large, so recursion would go thousands of calls deep.
- Most states will be needed anyway, so filling the whole table wastes nothing.
- You want to shrink memory later by keeping only the last row or last few cells.
Check yourself
Answer each one in your head first, then open it.
Why does the loop start at i = 2 and not at i = 0?
Cells 0 and 1 are base cases that are already right. Starting at 0 would read best[-1] and best[-2], which are undefined and turn the sums into NaN.
Why is best sized n + 1 and not n?
The top of the stairs is step n, one past the last priced step. A table of n cells has no cell for it.
In the trace, why is best[3] equal to 2 when cost[2] is 9?
Because the cheapest way onto step 3 jumps over step 2: stand on step 1, pay its 2, and jump two steps. Step 2's cost is never paid.
When could memoization do less work than tabulation?
When the question only reaches a small part of a huge state space. Memoization only solves the states it visits, while tabulation fills every cell in the table.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 4 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingGo further
You don't need these to start practicing. Open one when you want more.
Why it works
The reasoning that makes it always right
The loop keeps this promise: when it is about to fill best[i], the cells best[0] to best[i - 1] already hold the true cheapest cost to stand on each of those steps. The two free starting cells make it true at the beginning.
Filling best[i] keeps the promise. Any route to step i ends with a jump from i - 1 or from i - 2. If the cheapest route comes from i - 1, the part of it before that jump must be a cheapest route to i - 1; otherwise you could swap in a cheaper one and beat the cheapest route, which is impossible. That is optimal substructure. So the true answer is one of the two values the code compares, and Math.min picks it.
The order is the key choice in tabulation. Every cell reads only smaller indexes, so filling from left to right always reads finished cells. The loop runs once per step from 2 to n and then stops, and the promise at that point says best[n] is the cheapest cost to reach the top.
Going deeper
3 more ideas
Why the order matters
A cell is computed from other cells, so those cells must be final when it reads them. In this table, best[i] reads best[i - 1] and best[i - 2]. Going from left to right guarantees both are done. If you looped from right to left, you would read zeros that were never filled and get wrong answers without any error.
How much work tabulation does
The loop visits each cell once and does a fixed amount of work there, so the time is O(n). The table holds n + 1 numbers. Since each cell only reads the two before it, you could keep just two variables and use O(1) space.
Tabulation versus memoization
Memoization only solves the states the big question reaches, but it uses recursion. Tabulation solves every state in the table, but with a simple loop. When nearly every state is needed, which is common, tabulation is usually faster and safer. When only a few states are reachable out of a huge number, memoization does less work.
Work it by hand
Every step in a table, with why
Find the cheapest way to the top of a staircase with 6 priced steps. Each row fills one cell of best.
cost = [6, 2, 9, 3, 4, 8], so the top is step 6
| Step | i | fromOneBelow | fromTwoBelow | best[i] | What happens | Why |
|---|---|---|---|---|---|---|
| 1 | 0 and 1 | - | - | 0 and 0 | Base cells, free starts | The table is made with 0 in every cell. You may start on step 0 or step 1 for free, so those two cells are already right. |
| 2 | 2 | 0 + 2 = 2 | 0 + 6 = 6 | 2 | From one below wins | Standing on step 1 and paying its 2 is cheaper than standing on step 0 and paying its 6. |
| 3 | 3 | 2 + 9 = 11 | 0 + 2 = 2 | 2 | From two below wins | Step 2 is expensive to leave, so it is cheaper to jump from step 1 straight to step 3. |
| 4 | 4 | 2 + 3 = 5 | 2 + 9 = 11 | 5 | From one below wins | Both cells it reads were filled in earlier rows. Leaving step 3 costs only 3. |
| 5 | 5 | 5 + 4 = 9 | 2 + 3 = 5 | 5 | From two below wins | A jump from step 3 reaches step 5 for 5, the same total as reaching step 4. |
| 6 | 6 | 5 + 8 = 13 | 5 + 4 = 9 | 9 | From two below; loop ends | i equals n, so this is the top and the last cell. Line 9 returns best[6]. |
It returns 9: start on step 1 and pay 2, jump to step 3 and pay 3, move to step 4 and pay 4, then jump to the top. The loop ends because i has passed n, so every cell is filled.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Tabulation (bottom-up DP) | O(n) | O(n) | The default when nearly every state is needed: a simple loop with no recursion limit. |
| Memoization | O(n) | O(n) | When the recursive version is easier to write, the chain of states is short, or only a few states are reachable from the question. |
| Two rolling variables | O(n) | O(1) | When you only need the final number and the cells only look back a fixed distance. |
| Plain recursion | O(2^n) | O(n) | Only to find and test the recurrence on tiny inputs. |
Extra memory: O(n) for the n + 1 cells of best. Each cell only reads the two before it, so two variables would do, which gives O(1).
How the steps grow
- Tabulation (bottom-up DP), Memoization, Two rolling variables
O(n) - Plain recursion
O(2^n)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Tabulation (bottom-up DP) | 10 | 1,000 | 1 million |
| Memoization | 10 | 1,000 | 1 million |
| Two rolling variables | 10 | 1,000 | 1 million |
| Plain recursion | 1,024 | more than a quadrillion | more than a quadrillion |
Inputs that need care
5 tricky inputs
- An empty staircase:
cost = [] nis 0,bestis[0], the loop never runs, and it returns 0. You are already at the top.- One step:
cost = [5] - The top is step 1, a free starting point, so it returns 0 without paying anything.
- Two steps:
cost = [5, 7] - The loop runs once for
i = 2and picks the cheaper of 7 and 5, so it returns 5: start on step 0, pay 5, and jump to the top. - A step that can't be used, written as
Infinity Math.minjust avoids it, since any route through it costsInfinity. If every route is blocked, the answer isInfinity, which you may want to turn into-1.- 100,000 steps
- The loop handles it with no stack use at all. The same input would overflow the call stack in a memoized recursive version.
Variations and follow-ups
Twists interviewers add
Pull versus push
This code pulls: each cell reads the cells it depends on. You can also push: from each filled cell i, update best[i + 1] and best[i + 2] with Math.min. Both fill the same table. Pushing is handy when it's easier to list where a state leads than where it came from.
Rebuilding the route
Keep a second array from, where from[i] records whether the winner came from i - 1 or i - 2. After the loop, start at n and follow from back to step 0 or 1 to list the steps you stood on.
Tables in more dimensions
The same method fills 2-D tables, such as grid DP or the knapsack, usually row by row. The only rule is the same one: before you fill a cell, every cell it reads must be filled. For interval DP, that order is by interval length instead of by row.
Order from a graph
When the states don't line up by index, picture each state as a node with arrows to the states it reads. Any order that puts every arrow's target first works. That is a topological order of the graph, and it exists as long as no state depends on itself.
In an interview
Spot it, explain it, answer follow-ups
- Reach for it after you have a recurrence: if you can say what each state depends on and all those states are "smaller" in a clear order, you can fill a table in that order.
- Explain it in four parts: what one cell means, the base cells, the loop order and why it is safe, and which cell holds the answer. Interviewers listen for the order argument.
- Test it by filling the table by hand for a tiny input, such as
cost = [5, 7], and say each cell out loud. Then try[]and[5]to check the base cells and the table size. - Expect "can you do it in
O(1)space?". Keep two variables for the last two cells and shift them each loop, because no cell reads anything older.
Where frontend code uses it
The same idea in real UI code
- A spreadsheet-style table in a dashboard where each total cell is computed from the cells above it is a table filled in dependency order.
- Build tools process modules in an order where every dependency is handled before the modules that import it, the same "inputs first" rule tabulation follows.
- Precomputing a lookup table once, such as easing values for an animation at each frame, then reading from it later is tabulation.