Matrix traversal
Visit the cells of a grid in a planned order, such as row by row, column by column, along diagonals, or in a spiral.
O(rows * cols)Space O(1) besides the output10 practice problemsStep-through pictureThe idea
A matrix (or grid) is an array of rows, and each row is an array of cells. The cell in row r and column c is grid[r][c]. Rows count down from 0 at the top, and columns count across from 0 at the left.
Many grid problems are about visiting cells in a particular order: row by row to read it, column by column to flip it (transpose), along diagonals, or in a spiral from the outside in. The skill is keeping the row and column indexes straight and never stepping outside the grid.
The code here reads a grid in spiral order. It keeps four walls, top, bottom, left, and right, walks along each wall in turn, and moves that wall inward after it's done.
An everyday example
Mowing a lawn from the edge inward
You mow a rectangular lawn by going around its edge: along the top, down the right side, back along the bottom, and up the left side. Then the uncut part is a smaller rectangle, so you go around its edge, and so on until nothing is left. Each lap, the edge you've just mowed is no longer part of the lawn you have to think about.
| In the example | In the code |
|---|---|
| The edges of the uncut lawn | top, bottom, left, right |
| Mowing along the top edge | for (let c = left; c <= right; c++) on row top |
| That edge is done, so the lawn gets shorter | top++ |
| Nothing left to mow | top > bottom or left > right |
From the slow way to matrix traversal
- One obvious way to read a grid in a spiral is to simulate a walker. It moves in one direction and turns right when the next cell is outside the grid or already visited. That works, but it needs a
visitedgrid as big as the input and careful turning logic. - Look at the cells not yet read. After reading the whole top row, they form a smaller rectangle. After reading its right column, a smaller one still. The unread part is always a rectangle, so four numbers describe it:
top,bottom,left, andright. - So instead of tracking every cell, walk along one side of the rectangle and then move that side's wall inward by one. Top row, then right column, then bottom row backwards, then left column upwards: each pass shrinks the rectangle.
- When only a single row or column is left, the bottom-row and left-column passes would read cells a second time, so each gets a check first. Repeat until the walls cross: that loop is spiral matrix traversal.
How it works
- Set the walls:
top = 0,bottom= last row,left = 0,right= last column. - Read the top row from
lefttoright, then movetopdown by one. - Read the right column from
toptobottom, then moverightleft by one. - If rows are left (
top <= bottom), read the bottom row fromrightback toleft, then movebottomup. - If columns are left (
left <= right), read the left column frombottomup totop, then moveleftright. - Repeat while
top <= bottomandleft <= right.
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 spiralOrder(grid) {const result = [];let top = 0;let bottom = grid.length - 1;let left = 0;let right = grid[0].length - 1;while (top <= bottom && left <= right) {for (let c = left; c <= right; c++) result.push(grid[top][c]);top++;for (let r = top; r <= bottom; r++) result.push(grid[r][right]);right--;if (top <= bottom) {for (let c = right; c >= left; c--) result.push(grid[bottom][c]);bottom--;}if (left <= right) {for (let r = bottom; r >= top; r--) result.push(grid[r][left]);left++;}}return result;}
How fast it is
| Time | O(rows * cols) |
|---|---|
| Extra memory | O(1) besides the output |
Each cell is read exactly once. Only the four walls and loop counters are stored.
- Best case
O(rows × cols)Even a 1 × 1 grid reads its one cell. The output holds every cell, so no input can skip any.
- Average case
O(rows × cols)Each cell is read once, plus a few wall checks per lap.
- Worst case
O(rows × cols)A square grid needs the most laps, about half its side, but the total reads are still one per cell.
Common mistakes
- Swapping row and column:
grid[c][r]instead ofgrid[r][c]. Name the loop variablesrandcto keep them straight. - Leaving out the
top <= bottomandleft <= rightchecks inside the loop, which reads a single leftover row or column twice. - Assuming the grid is square. Use
grid.lengthfor rows andgrid[0].lengthfor columns. - Reading a neighbor before checking it's inside the grid.
grid[-1][c]throws an error.
When to use it
Look for these clues in a problem:
- The input is a 2D array: a board, an image, a spreadsheet, a seat map.
- The problem describes a reading order: spiral, diagonal, zigzag, layer by layer.
- You need to rotate, flip, or transpose a grid.
- Each cell's answer depends on its neighbors (up, down, left, right).
Check yourself
Answer each one in your head first, then open it.
In the trace's second lap, why does the bottom-row pass read nothing even though top <= bottom?
right has dropped to 0 while left is 1, so the loop from right down to left has no steps.
What would [[3, 8, 1]] return without the top <= bottom check?
[3, 8, 1, 8, 3]. The bottom-row pass would read row 0 again, backwards, from right = 1 down to left = 0.
How many laps does the while loop make for a 4 × 3 grid?
Two. The first reads the outer ring of 10 cells, and the second reads the last 2.
Why is the extra space O(1)?
The unread part is always a rectangle, so four numbers describe it. No per-cell visited flags are needed.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 10 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingmedium
Go 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 one rule: at the top of each lap, the unread cells are exactly rows top to bottom in columns left to right, and result holds every other cell in spiral order. At the start the walls are the grid's edges and result is empty, so it holds.
Each pass reads one edge of that rectangle and then moves the matching wall, so the cells it read are now outside the walls. The right-column pass starts at the new top, so the top-right corner isn't read twice. The checks before the bottom-row and left-column passes stop a lone row or column from being read again in the other direction.
Every lap moves top down by one, so the loop can't run forever. When the walls cross, the rectangle is empty, which by the rule means result holds every cell exactly once, in the right order.
Going deeper
4 more ideas
Why the walls never let a cell be read twice
After a side is read, its wall moves inward, so the cells on that side are outside the walls from then on. The checks before the bottom row and left column matter when only one row or one column is left: without them, the code would read that row forwards and then backwards again.
Other common orders
Transposing swaps rows and columns: result[c][r] = grid[r][c]. Rotating 90 degrees clockwise puts grid[r][c] at result[c][n - 1 - r]; in place, that's a transpose followed by reversing each row. Cells on the same diagonal share r - c, and cells on the same anti-diagonal share r + c.
Neighbors and edges
To visit a cell's four neighbors, loop over a list of moves like [[-1, 0], [1, 0], [0, -1], [0, 1]] and skip any neighbor where nr < 0, nr >= rows, nc < 0, or nc >= cols. Reading grid[-1] gives undefined, and reading undefined[c] throws, so check the bounds first.
Cost
Every traversal here visits each cell a constant number of times: O(rows * cols) time. The spiral uses only the four walls besides the output, so O(1) extra space.
Work it by hand
Every step in a table, with why
Read a grid with 4 rows and 3 columns in spiral order. The second lap shows why the inner checks matter.
grid = [[7, 1, 8], [2, 9, 4], [6, 3, 5], [0, 4, 2]]
| Step | Side | Reads | top | bottom | left | right | Why |
|---|---|---|---|---|---|---|---|
| 1 | Top row | 7, 1, 8 | 1 | 3 | 0 | 2 | Read row 0 from left to right, then move top down to 1. |
| 2 | Right column | 4, 5, 2 | 1 | 3 | 0 | 1 | Read column 2 from the new top to bottom, then move right in to 1. |
| 3 | Bottom row | 4, 0 | 1 | 2 | 0 | 1 | top <= bottom, so read row 3 backwards from right to left, then move bottom up to 2. |
| 4 | Left column | 6, 2 | 1 | 2 | 1 | 1 | left <= right, so read column 0 upwards from bottom to top, then move left to 1. The first lap is done. |
| 5 | Top row | 9 | 2 | 2 | 1 | 1 | The rectangle left is rows 1 to 2 of column 1. Read 9 and move top to 2. |
| 6 | Right column | 3 | 2 | 2 | 1 | 0 | Read 3 from row 2 of column 1. right drops to 0, below left. |
| 7 | Bottom row | nothing | 2 | 1 | 1 | 0 | top <= bottom (2 <= 2) is true, but the loop from right = 0 down to left = 1 is empty, so nothing is read. bottom moves to 1. |
| 8 | Left column | skipped | 2 | 1 | 1 | 0 | left <= right is 1 <= 0, false, so the pass is skipped. Now top > bottom, and the while loop ends. |
The result is [7, 1, 8, 4, 5, 2, 4, 0, 6, 2, 9, 3]: all 12 cells, each read once, in 2 laps.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Matrix traversal | O(rows × cols) | O(1) | A plain spiral or layer order with the least memory, using four walls. |
| Walker with a visited grid | O(rows × cols) | O(rows × cols) | The path has extra rules, like blocked cells, that four walls can't describe. |
| Read the top row, rotate the rest, repeat | O(n^3) | O(n²) | Very short code for tiny grids. Rebuilding the rest after every row costs about n³ steps for an n × n grid. |
Extra memory: O(1) besides the output: four walls and a loop counter. The walker approach needs an extra O(rows × cols) grid of visited flags.
How the steps grow
- Matrix traversal, Walker with a visited grid
O(rows × cols) - Read the top row, rotate the rest, repeat
O(n^3)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Matrix traversal | 100 | 1 million | 1 trillion |
| Walker with a visited grid | 100 | 1 million | 1 trillion |
| Read the top row, rotate the rest, repeat | 1,000 | 1 billion | more than a quadrillion |
Inputs that need care
5 tricky inputs
- One row:
grid = [[3, 8, 1]] - The top pass reads all three,
toppassesbottom, the right-column loop is empty, and both checks keep the other passes from reading anything again. Result:[3, 8, 1]. - One column:
grid = [[3], [8], [1]] - The top pass reads
3, the right pass reads8, 1, andrightdrops belowleft. The bottom loop is empty and the left pass is skipped. Result:[3, 8, 1]. - An empty grid:
grid = [] grid[0].lengththrows, becausegrid[0]isundefined. Return[]first whengrid.lengthis 0.- Rows with no cells:
grid = [[]] rightstarts at-1, soleft <= rightis false at once and the result is[].- Rows of different lengths
- The walls assume every row has
grid[0].lengthcells. Shorter rows giveundefinedin the result, so check the shape first.
Variations and follow-ups
Twists interviewers add
Fill a grid in spiral order
Use the same four walls, but write 1, 2, 3, ... into the cells instead of reading them. This builds an n × n spiral of numbers.
Rotate a square layer by layer
To rotate a square grid 90 degrees in place, handle one ring at a time, from the outside in. For each position on the ring, move four cells around in one cycle: top to right, right to bottom, bottom to left, left to top.
A walker with direction lists
Keep dr = [0, 1, 0, -1] and dc = [1, 0, -1, 0] with a direction index, and turn when the next cell is outside or visited. It needs the visited grid, but it also handles walks with obstacles or snake-like paths.
In an interview
Spot it, explain it, answer follow-ups
- Spot it from "spiral order", "clockwise", "layer by layer", or "rotate the image".
- Explain the walls before coding: "the unread cells always form a rectangle, so I keep its four edges and move one inward after each side".
- Test by hand: one row, one column, and a grid that isn't square. All three hit the inner checks.
- A common follow-up is "rotate the matrix 90 degrees in place". Answer: transpose it and reverse each row, or rotate four cells at a time ring by ring.
Where frontend code uses it
The same idea in real UI code
- Canvas pixel data from
getImageDatais a flat array where pixel(x, y)starts at index(y * width + x) * 4, so image filters are matrix traversals. - Spreadsheet and data-grid components move the active cell with arrow keys, checking the grid's edges.
- Rotating or flipping an image or a game board by 90 degrees in a browser editor.
- Calendar month views lay out days in a grid of weeks and weekdays, read row by row.