Tabulation builds the DP answer bottom-up: make a table with one cell per subproblem, fill in the base cases, then fill every other cell in an order where everything it depends on is already filled. No recursion, no call stack — just loops. When the last cell is filled, it holds the answer.
dp[i][j] = dp[i−1][j−1] + dp[i−1][j] reads two cells and writes one.Each cell is filled from the two cells above it, row by row.
function binomial(n, k) { // dp[i][j]: the number of ways to choose j items from i const dp = Array.from({ length: n + 1 }, () => new Array(k + 1).fill(0)); for (let i = 0; i <= n; i++) { dp[i][0] = 1; // choose nothing: one way for (let j = 1; j <= Math.min(i, k); j++) { dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; // item i is in, or it's out } } return dp[n][k];}Time O(n · k), space O(n · k). The recurrence is Pascal's rule: to choose j of the first i items, either item i is chosen (and j − 1 come from the rest) or it isn't (and all j do).
Draw an arrow from every cell a cell reads to the cell itself. Any order that fills cells after everything pointing into them works. The common shapes:
dp[i] reads dp[i-1], dp[i-2] left to right (Fibonacci, Climbing Stairs, House Robber)
dp[i] reads dp[i+1], dp[i+2] right to left (suffix problems, Decode Ways backwards)
dp[r][c] reads above and to the left row by row (grid paths, LCS, edit distance)
dp[l][r] reads shorter ranges inside by increasing length (palindromes, burst balloons)
dp[amount] reads smaller amounts amount 0, 1, 2, ... (coin change)Fibonacci, for example, is a 1D table filled left to right:
function fib(n) { const dp = new Array(n + 1).fill(0); if (n >= 1) dp[1] = 1; for (let i = 2; i <= n; i++) dp[i] = dp[i - 1] + dp[i - 2]; // both are already filled return dp[n];}