Arrays · Matrix Techniques

Grid Tabulation DP

Filling a 2D table left-to-right, top-to-bottom, where each cell's answer is built from the cells directly above and to the left of it.

  • 5 min read
  • 12 practice questions
  • 3 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: one rolling row
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

Grid tabulation fills a table the shape of the grid. Each cell holds an answer, such as the number of paths to it or the cheapest cost to reach it. That answer is built from the cells above it and to its left, which were filled first. That turns an exponential "try every path" search into one O(rows x cols) pass. It's also the shape behind edit distance, longest common subsequence and many other 2D DP problems.

We'll go from simple to more technical:

  1. The pattern: base cases on the edges, then each cell from its neighbors.
  2. A second flavor: one rolling row instead of the whole table.
  3. Where the same table-filling runs in production (diff tools, sequence alignment).
  4. The bugs: wrong base cases, the wrong fill order, and Python's shared rows.

The approach

How many ways are there to walk from the top-left corner to the bottom-right, moving only right or down? Trying every path is exponential. But you can only arrive at a cell from above or from the left. So the number of ways to reach a cell is the ways to reach the cell above plus the ways to reach the cell to its left. Cells on the top row or left column can be reached only one way: straight along the edge.

Filling a grid with 3 rows and 4 columns, row by row:

Column 0Column 1Column 2Column 3
Row 01111
Row 11234
Row 213610

The bottom-right corner can be reached 10 ways. Its 10 is 4 (from above) plus 6 (from the left).

Java

int[][] dp = new int[rows][cols];
for (int r = 0; r < rows; r++) {
    for (int c = 0; c < cols; c++) {
        if (r == 0 || c == 0) {
            dp[r][c] = 1;                          // base case: one way along an edge
        } else {
            dp[r][c] = dp[r - 1][c] + dp[r][c - 1]; // combine the two cells this one depends on
        }
    }
}
return dp[rows - 1][cols - 1];

Python

def unique_paths(rows: int, cols: int) -> int:
    dp = [[0] * cols for _ in range(rows)]
    for r in range(rows):
        for c in range(cols):
            if r == 0 or c == 0:
                dp[r][c] = 1                            # base case: one way along an edge
            else:
                dp[r][c] = dp[r - 1][c] + dp[r][c - 1]  # combine the two cells this one depends on
    return dp[rows - 1][cols - 1]

It's Pascal's Triangle turned on its side. Each cell combines cells already filled, so one ordered pass builds the whole table, with no recursion.

A second flavor: one rolling row

A cell needs only the cell above it and the cell to its left, never anything two rows back. So one row, updated in place, is enough:

Java

int[] row = new int[cols];
Arrays.fill(row, 1);                 // row 0: one way along the top edge
for (int r = 1; r < rows; r++) {
    for (int c = 1; c < cols; c++) {
        row[c] = row[c] + row[c - 1]; // row[c] is still "row r-1"; row[c-1] is already "row r"
    }
}
return row[cols - 1];

Python

def unique_paths(rows: int, cols: int) -> int:
    row = [1] * cols                     # row 0: one way along the top edge
    for _ in range(1, rows):
        for c in range(1, cols):
            row[c] = row[c] + row[c - 1] # row[c] is still "row r-1"; row[c-1] is already "row r"
    return row[cols - 1]

Look at row[c] + row[c - 1] as the loop moves left to right. row[c] hasn't been updated yet in this pass, so it still holds the value from the row above. row[c - 1] was just updated, so it holds the value to the left in the current row. One array plays both parts, and memory drops to O(cols).

Where you'll see it

Diff tools, spell checkers and DNA sequence alignment all fill a table like this. Each cell compares two prefixes, one of each string, and is built from three neighboring cells. Counting paths is the simplest member of that family.

Which tool for which job?

You needUse
Count paths moving right/downdp[r][c] = dp[r-1][c] + dp[r][c-1]
Count paths, no obstacles, one numberthe closed form C(rows + cols - 2, rows - 1)
Cheapest right/down pathgrid[r][c] + min(up, left)
Only the final answerone rolling row, O(cols)
The path itself, not just its costkeep the full table and walk back
Moves in all four directionsDijkstra (or BFS for equal costs) - not grid DP

Common mistakes

MistakeWhat happensDo this instead
Edges initialized to 0 instead of 1 (counting)every count is 0 or wrongone way along each edge, until an obstacle
Filling in an order that reads unfilled cellsstale zeros flow throughtop-to-bottom, left-to-right
[[0] * cols] * rows in Pythonevery row is the same list[[0] * cols for _ in range(rows)]
An int path count in Javaoverflows from an 18 x 18 gridlong (to 34 x 34), then modular or big integers
Grid DP when moves can go up or leftit misses cheaper pathsa shortest-path algorithm

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Why do the first row and first column start at 1, not 0?

Show answer

There's exactly one way to reach a cell on the top or left edge: go straight along the edge. That's one real path. With 0 there, every cell that adds them in would come out too small, and in fact every count would be 0.

2. The table is filled column by column instead of row by row. Does it still work? Which order would break it?

Show answer

It still works. Column by column, the column to the left is finished and the cell above was filled just before. All a cell needs is that both of its inputs exist first. Filling bottom to top or right to left would break it: the cell above or to the left would still hold its default 0.

3. Why is one rolling row enough for the final answer?

Show answer

A cell depends only on the row directly above and on cells to its left in the same row. Once row r - 1 has been used to build row r, nothing needs it again. So one array, updated left to right, can hold both.

Key takeaways

  • Each cell is built from cells already filled: here, the one above plus the one to the left.
  • The edges need the right base case, 1 for counting paths. A 0 there spoils every cell after it.
  • Fill in an order where both inputs exist first. Row by row, left to right is the simplest.
  • If no cell looks back more than one row, one rolling row is enough: O(cols) memory.
  • Minimum path sum and edit distance fill tables the same way, with a different combining rule and base case.

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before practice or an interview, and open the full lesson for anything that doesn't ring a bell.

Pick the tool

You needUse
Count right/down pathsup + left
No obstacles, one numberC(r + c - 2, r - 1)
Cheapest pathcell + min(up, left)
Less memoryone rolling row
The path itselfthe full table, walk back
Four-direction movesDijkstra / BFS

Snippets worth knowing by heart

Java

long[] row = new long[cols];
row[0] = grid[0][0] == 1 ? 0 : 1;
for (int r = 0; r < rows; r++)
    for (int c = 0; c < cols; c++) {
        if (grid[r][c] == 1) row[c] = 0;            // obstacle
        else if (c > 0) row[c] += row[c - 1];       // row[c] is still "up"; row[c - 1] is "left"
    }
return row[cols - 1];

Python

row = [0] * cols
row[0] = 0 if grid[0][0] == 1 else 1
for r in range(rows):
    for c in range(cols):
        if grid[r][c] == 1:
            row[c] = 0                  # obstacle
        elif c > 0:
            row[c] += row[c - 1]        # row[c] is still "up"; row[c - 1] is "left"
return row[-1]

The traps

  • Edges at 0 instead of 1: every path count comes out 0. There's one way along each edge, until an obstacle.
  • The wrong fill order: cells read neighbors that aren't filled yet. Fill top to bottom, left to right.
  • [[0] * cols] * rows makes every row the same list in Python. Use [[0] * cols for _ in range(rows)].
  • int path counts: they overflow from an 18 x 18 grid. Use long, then a modulus or big integers.
  • Four-direction moves: grid DP assumes moves only right and down, so it misses cheaper paths. Use a shortest-path algorithm.

Staff-level points, one line each

  • Any fill order consistent with the dependencies works - they form a DAG.
  • n x n path counts overflow int at n = 18, long at n = 35.
  • Memoized recursion on 2,000 x 2,000 raised RecursionError; tabulation doesn't recurse.
  • The path needs the full table; a rolling row keeps only costs.
  • Four directions: DP said 16, Dijkstra found 14.

Say it in an interview

  • "Each cell depends only on the cell above and the cell to its left, which are already filled."
  • "The edges have exactly one path each."
  • "I only need the previous row, so O(cols) space."
  • "If moves could go in any direction, I'd switch to Dijkstra."

Practice

This skill comes with 12 multiple-choice questions that tell you which misconception a wrong answer reveals and 3 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Grid Tabulation DP free

Grid Unique Paths

MediumProCoding problemJava · Python

A delivery robot starts in the top-left cell of a grid with m rows and n columns, and has to reach the bottom-right cell. It can only step right or down, one cell at a time. Count the different routes it could take.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Minimum Path Sum in a Grid

MediumProCoding problemJava · Python

Each cell of an R x C grid has a non-negative cost. Walk from the top-left cell to the bottom-right cell, stepping only right or down, and pay the cost of every cell you stand on, both ends included. Return the cheapest total.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Unique Paths with Obstacles

MediumProCoding problemJava · Python

The same robot, with the same moves (right or down only), but now some cells are blocked: 1 marks an obstacle and 0 an open cell. Count the routes from the top-left cell to the bottom-right cell that never enter a blocked cell. If either corner is blocked, the answer is 0.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Grid Tabulation DP free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime