Hello! In our previous lesson, we dove into dynamic programming by mastering top-down memoization—a powerful technique that uses recursion and caching to avoid redundant work. You learned a systematic recipe to convert a slow, brute-force recursive function into an efficient one.
Today, we'll explore the second major DP implementation strategy: bottom-up tabulation. This approach flips the problem on its head. Instead of starting from the top and recurring downwards, we'll start with the simplest base cases and iteratively build our way up to the final solution. For many developers, this iterative style feels more direct and grounded than recursion, and it often leads to more efficient code by eliminating recursion overhead.
Your learning outcome for this lesson is to convert a memoized recurrence into bottom-up tabulation. We will treat this as a structured, mechanical process, transforming a solution you already know how to write (memoized recursion) into its iterative counterpart.
From Top-Down to Bottom-Up: A Shift in Perspective
In memoization, a call to solve(n) triggers recursive calls to solve(n-1) and solve(n-2), which in turn call their predecessors, and so on, until a base case is hit. The results then bubble back up the call stack.
Tabulation works in the opposite direction. It asks: "What's the smallest possible subproblem I can solve?" It solves it, stores the answer in a table (usually an array), and then uses that result to solve the next smallest subproblem. This continues until the target problem is solved.
The following image provides a great visual summary of this contrast, using the Fibonacci sequence as an example.

As you can see, both methods calculate the same set of subproblem solutions (fib(0) through fib(5)), but they arrive at them in a different order.
The Conversion Recipe: From Memoization to Tabulation
Since you already have a framework for finding the recurrence relation (state, transition, base cases) and implementing it with memoization, we can define a clear, step-by-step process to convert that memoized solution into a tabulated one.
Let's use the memoized Fibonacci function as our running example:
// Previous lesson's memoized solution
function fib(n: number, memo: Map<number, number> = new Map()): number {
if (memo.has(n)) return memo.get(n)!;
if (n <= 1) return n;
const result = fib(n - 1, memo) + fib(n - 2, memo);
memo.set(n, result);
return result;
}
Here is the recipe to convert this into a tabulated solution:
-
Design the DP Table: Identify the parameters of your recursive function. For
fib(n), the state is justn. This determines the dimensions of your table. Sincengoes from0to the target value, we can use a 1D array, let's call itdp, of sizen + 1.dp[i]will store the result offib(i). -
Determine the Iteration Order: This is the most critical step. Look at the dependencies in your recurrence relation. In
fib(n) = fib(n-1) + fib(n-2), calculating the result fornrequires the results for smaller values (n-1andn-2). This tells us we must compute the values for ourdptable in increasing order of the index. Our loop must go from the smallest subproblems to the largest. -
Seed the Table with Base Cases: The base cases from your recursive function become the initial values in your table. For Fibonacci,
if (n <= 1) return n;translates to settingdp[0] = 0anddp[1] = 1. -
Translate the Recurrence into a Loop: Convert the recursive logic into an iterative one. The line
fib(n - 1, memo) + fib(n - 2, memo)becomes a table lookup inside a loop:dp[i] = dp[i-1] + dp[i-2]. -
Extract the Final Answer: The final result, which was the output of your top-level recursive call
fib(n), is now simply the last value computed in your table,dp[n].
The following article provides a clear, step-by-step guide to this thought process.
Mastering Recursive and Dynamic Programming (DP ...
This article formalizes the bottom-up DP approach. Focus on the sections that define bottom-up DP and walk through the step-by-step framework.
Please read the introduction to Bottom-Up DP. Then, carefully study the four steps provided: Identifying Subproblems, Designing the DP Table, Iterative Filling, and Extracting the Result. Finally, see how these steps are applied in the Fibonacci example. This maps directly to our conversion recipe.
Applying this recipe, our fib function transforms like this:
function fibTabulated(n: number): number {
if (n <= 1) return n;
// 1. Design DP table
const dp = new Array(n + 1);
// 3. Seed with base cases
dp[0] = 0;
dp[1] = 1;
// 2. & 4. Iterate in correct order and translate recurrence
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
// 5. Extract final answer
return dp[n];
}
This version is often preferred in production code and competitive programming because it's typically faster (no function call overhead) and avoids the risk of stack overflow errors that can happen with deep recursion.
A More General Method for Determining Loop Order
For simple problems like Fibonacci, the iteration order is obvious. But what about problems with more complex states, like solve(index, remainingCapacity)? The DecodingIntuition channel offers a brilliant and systematic way to figure out the loop structure by analyzing dependencies for each parameter separately.
Recursive to iterative dynamic programming in 3 steps!
This video presents a rigorous 3-step process to convert a 2D recursive DP into an iterative one. Focus on the logic for determining array bounds and loop order, as it's a powerful and generalizable skill.
Watch the section on determining parameter bounds to see how the state space of the recursion defines the size of your DP table. Pay close attention to the crucial part on figuring out the loop order. The key idea is to look at how each parameter changes in the recursive calls (e.g., i becomes i+1, x becomes x - coins[i]) to decide if the loop for that parameter should go forwards or backwards. Finally, see how this all comes together when the recursive calls are replaced with array lookups.
The core takeaway is that you can decide the direction of each loop independently. If dp[i] depends on dp[i+1] (a larger index), the i loop must run backwards. If dp[i] depends on dp[i-1] (a smaller index), the i loop must run forwards.
Worked Example: Climbing Stairs (Tabulated)
Let's apply this conversion recipe to the "Climbing Stairs" problem from our last lesson. The recurrence was ways(n) = ways(n-1) + ways(n-2).
- DP Table: The state is the number of steps,
n. We need a 1D array,dp, of sizen + 1. - Iteration Order:
dp[i]will depend ondp[i-1]anddp[i-2]. So, we must iterate withiincreasing. - Base Cases: We had
ways(1) = 1andways(2) = 2. So, we'll setdp[1] = 1anddp[2] = 2. Note that DP tables are often 0-indexed, so we might need to adjust. A common pattern is to make the array of sizen+1and use 1-based indexing for clarity. - Loop: The loop will run from
i = 3ton, and the body will bedp[i] = dp[i-1] + dp[i-2]. - Answer: The result is in
dp[n].
The following video provides an animated walkthrough of exactly this solution.
Lean Dynamic Programming with Animations – Full Course for Beginners
This video from freeCodeCamp explains tabulation using the staircase problem. It's a great visual reinforcement of the process we just outlined.
Watch the section explaining the tabulation approach for the staircase problem. It clearly shows how the dp array is created, seeded with base cases, and filled iteratively.
Memoization vs. Tabulation: Which to Choose?
Now that you know both techniques, when should you use one over the other?
| Feature | Top-Down (Memoization) | Bottom-Up (Tabulation) |
|---|---|---|
| Logic | Recursive, starts from target | Iterative, starts from base cases |
| Pros | - Often more intuitive to write from a recurrence. - Only computes states that are actually reached. | - No recursion overhead, generally faster. - No risk of stack overflow. - Loop structure can reveal space optimizations. |
| Cons | - Can have significant recursion overhead. - Risk of stack overflow on deep state spaces. | - May be less intuitive to set up iteration order. - Might compute states that are not needed for the final answer. |
| Best Fit | Problems where the state transitions are complex or sparse (e.g., partitioning problems), making an iterative order hard to define. | Problems where all subproblems must be solved anyway and there is a clear, sequential dependency (e.g., fib(n), climbStairs(n)). |
For a concise summary, let's revisit the freeCodeCamp video.
Lean Dynamic Programming with Animations – Full Course for Beginners
This segment directly compares the two approaches and gives practical advice on when to choose one.
Please watch the summary that compares memoization and tabulation. This will help solidify your understanding of the trade-offs.
As a senior developer, you'll likely find that tabulation often feels more "solid" and performant, aligning well with standard iterative programming patterns you use daily.
Conclusion
You've now learned the two fundamental implementation strategies for dynamic programming. While memoization gets you a correct solution quickly from a recurrence, tabulation gives you an iterative, often more performant solution.
Key takeaways from this lesson:
- Tabulation is a bottom-up, iterative DP approach that builds a table of solutions from the base cases up to the final answer.
- You can mechanically convert a memoized solution to a tabulated one by designing a table based on the state, determining the iteration order from dependencies, seeding base cases, and translating the recurrence into a loop.
- Tabulation avoids recursion overhead and stack overflow risk, making it very efficient for problems with a clear, dense state space.
- The choice between memoization and tabulation is a practical trade-off between implementation convenience and runtime performance.
In our next lesson, we'll leverage the structure of tabulation to unlock a powerful optimization. You'll learn how to reduce a dynamic program's memory when each state depends only on recent states, taking solutions from space to space.
Can't find a good explanation? Sign up and we'll make it for you
Sign up