Dynamic Programming: Code Templates in 6 Languages
If you have not read the concept guide yet, start there for the state definition rules and complexity analysis. This page gives you the memoization and tabulation blueprints you can memorize and adapt in an interview. The main template is the recursive form with a cache. Each variation after it shows one way the same skeleton changes shape.
Main Template: Memoization (Top-Down)
This is the top-down form of dynamic programming. It solves the problem the same way the naive recursion would, but it stores every answer on the way out of the recursion so no state is computed twice. Each call checks the cache first, and the recursion only does real work on the first visit to a state.
Use this for Min Cost Climbing Stairs .
function minCostClimbingStairs(cost) {
const memo = new Array(cost.length).fill(-1);
// dfs(i) is the cheapest way to climb from step i to the top
function dfs(i) {
if (i >= cost.length) return 0; // past the top, nothing left to pay
if (memo[i] !== -1) return memo[i]; // already paid this climb once
memo[i] = cost[i] + Math.min(dfs(i + 1), dfs(i + 2));
return memo[i];
}
// the top can be reached from step 0 or step 1
return Math.min(dfs(0), dfs(1));
}Code Breakdown
Key Variables
i: the state. The step you are standing on. Everything before stepiis already paid for and does not affect the rest of the climb.cost[i]: the price of standing on stepi. It is paid exactly once per path, when that step is reached.memo: the cache.memo[i]holds the cheapest climb from stepiafter the first computation. The-1sentinel means “not computed yet”.dfs(i): the recurrence. The cheapest way from stepito the top iscost[i]plus the cheaper of the two steps ahead.
Visual Mechanism
graph TD
A["dfs(i)"] --> B{"i >= n?"}
B -->|Yes| Z["return 0"]
B -->|No| C{"memo[i] set?"}
C -->|Yes| Y["return memo[i]"]
C -->|No| D["compute cost[i] + min(dfs(i+1), dfs(i+2))"]
D --> E["store result in memo[i]"]
E --> Y
Critical Sections
The base case is i >= cost.length, which returns 0. Standing past the top costs nothing, and it is the only state that never needs the cache.
The cache check happens before any work. That single guard is what separates DP from the exponential recursion. The same function body runs, but each state is solved once and every repeat visit is a lookup.
The recurrence reads the two future states and stores the result before returning. The answer for the whole problem is min(dfs(0), dfs(1)), because the top can be reached from either starting step.
Variations
1. Tabulation (Bottom-Up)
When the recursion shape is simple, you can drop the cache and fill the table directly in dependency order. This version computes the same costs but walks backward from the top, so every state reads two already-final neighbors.
Time
Use this for Min Cost Climbing Stairs .
function minCostClimbingStairs(cost) {
const n = cost.length;
const dp = new Array(n + 2).fill(0);
// dp[i] is the cheapest way from step i to the top.
// Fill backward so each step reads the two steps ahead,
// which are already final.
for (let i = n - 1; i >= 0; i--) {
dp[i] = cost[i] + Math.min(dp[i + 1], dp[i + 2]);
}
return Math.min(dp[0], dp[1]);
}
graph LR
A["dp[n] = dp[n+1] = 0"] --> B["i from n-1 down to 0"]
B --> C["dp[i] = cost[i] + min(dp[i+1], dp[i+2])"]
C --> D["answer: min(dp[0], dp[1])"]
2. Space-Optimized (Two Variables)
Once you notice that state i only reads states i-1 and i-2, the full table is wasteful. Two rolling variables carry the same information. This is the shape behind most 1D problems, and it is what interviewers mean when they ask for the space-optimized solution.
Time
Use this for House Robber .
function rob(nums) {
if (nums.length === 0) return 0;
let prev2 = 0; // best total two houses back
let prev1 = nums[0]; // best total up to the previous house
for (let i = 1; i < nums.length; i++) {
// rob i: prev2 + nums[i]. skip i: keep prev1.
const current = Math.max(prev1, prev2 + nums[i]);
prev2 = prev1;
prev1 = current;
}
return prev1;
}
graph LR
A["prev2: best up to i-2"] --> B["prev1: best up to i-1"]
B --> C["current = max(prev1, prev2 + nums[i])"]
C --> D["shift: prev2 = prev1, prev1 = current"]
3. Look-Back (O(N^2))
Some states depend on every earlier state, not just the previous two. Each dp[i] scans all j < i and extends the best run that can accept nums[i]. The table is still 1D, but every cell does linear work, which is where the quadratic bound comes from.
Time
Use this for Longest Increasing Subsequence .
function lengthOfLIS(nums) {
const dp = new Array(nums.length).fill(1);
let best = 1;
for (let i = 1; i < nums.length; i++) {
for (let j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
// nums[i] extends the increasing run ending at j
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
best = Math.max(best, dp[i]);
}
return best;
}
graph TD
A["for each i, dp[i] starts at 1"] --> B["for each j < i"]
B --> C{"nums[j] < nums[i]?"}
C -->|Yes| D["dp[i] = max(dp[i], dp[j] + 1)"]
C -->|No| E["next j"]
D --> E
E --> F["track the best dp[i] seen"]
4. Two-Dimensional Table
Two inputs mean two indices, and the table becomes a grid. Each cell reads its diagonal, its left neighbor, and its upper neighbor. This is the shape of longest common subsequence, edit distance, and every two-string DP problem.
Time
Use this for Longest Common Subsequence .
function longestCommonSubsequence(text1, text2) {
const m = text1.length, n = text2.length;
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (text1[i - 1] === text2[j - 1]) {
// a matching pair extends the best prefix of both
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
// no match: keep the longer best prefix from
// either text1 or text2
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
graph TD
A["dp[i][j]"] --> B{"text1[i-1] == text2[j-1]?"}
B -->|Yes| C["dp[i-1][j-1] + 1"]
B -->|No| D["max(dp[i-1][j], dp[i][j-1])"]
Constraint DP (Knapsack)
If the problem adds a capacity or a target sum, the state gains a second dimension for the remaining budget. That shape is distinct enough to have its own pattern page. The Knapsack templates cover the 0/1 and unbounded variants with full code in all 6 languages.
Now head to the practice problems to apply these templates to real interview questions.