Spiral Matrix

Spiral Matrix tests whether you can keep careful track of where you are. The idea is simple. You read the grid in a spiral, like peeling an onion. But the hard part is the bookkeeping. If your boundaries are off by one, you read a cell twice or you skip one. The interviewer wants to see clean, exact control of those edges.

🎯 The Problem

You get a matrix and you must read it in a spiral. Here are the rules.

  • A matrix is a grid of numbers with rows and columns.
  • Read every number in spiral order.
  • Go right across the top, then down the right side.
  • Then left across the bottom, then up the left side.
  • Move inward and repeat. Return the numbers in that order as a list.

Look at this small grid. You start at the top left and spiral inward to the center.

Input:
1 2 3
4 5 6
7 8 9
Output: [1, 2, 3, 6, 9, 8, 7, 4, 5]
Explanation: top row, right column, bottom row, left column, then the center.

Here is the path drawn out. Each step points to the next cell we visit.

1

2

3

6

9

8

7

4

5

🐒 Approach 1: Walk With a Visited Grid (Brute Force)

The idea in one line: walk cell by cell, mark where you have been, and turn right whenever you cannot go straight.

The idea:

  • Keep a second grid of true and false.
  • Walk in one direction. Mark each cell as visited.
  • When the next step goes off the edge or hits a visited cell, turn right.
  • Stop when every cell is visited.

Why it is weak:

  • It needs an extra grid as big as the input.
  • The space is O(m Γ— n).
  • The turning logic is fiddly and easy to get wrong.

Here is the visited-grid code:

spiral_matrix_visited.py
def spiral_order(matrix):
rows, cols = len(matrix), len(matrix[0])
seen = [[False] * cols for _ in range(rows)]
dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)]
r = c = direction = 0
answer = []
for _ in range(rows * cols):
answer.append(matrix[r][c])
seen[r][c] = True
nr, nc = r + dirs[direction][0], c + dirs[direction][1]
if nr < 0 or nc < 0 or nr == rows or nc == cols or seen[nr][nc]:
direction = (direction + 1) % 4
nr, nc = r + dirs[direction][0], c + dirs[direction][1]
r, c = nr, nc
return answer

⚑ Approach 2: Four Shrinking Boundaries (Best)

The idea in one line: track four edge numbers and pull them inward one lap at a time, no extra grid.

The idea:

  • Keep four numbers called boundaries: top, bottom, left, right.
  • They mark the edges of the part you still need to read.
  • At the start they box in the whole matrix.

How one lap works:

  • Read the top row from left to right. Then move top down by one.
  • Read the right column from top to bottom. Then move right left by one.
  • Read the bottom row from right back to left. Then move bottom up by one.
  • Read the left column from bottom up to top. Then move left right by one.

How it finishes:

  • After each lap the box shrinks.
  • Keep going while top is not past bottom and left is not past right.
  • Guard the bottom row and left column reads. That stops a lone middle row or column from being read twice.

Why it is fast:

  • It uses only four numbers, not a whole grid.
  • Each cell is read exactly once.
  • The extra space is O(1).

Steps to Solve

  1. Set top = 0, bottom = lastRow, left = 0, right = lastCol.
  2. While top <= bottom and left <= right, do the four moves below.
  3. Read the top row from left to right. Then do top = top + 1.
  4. Read the right column from top to bottom. Then do right = right - 1.
  5. If top <= bottom, read the bottom row from right to left. Then do bottom = bottom - 1.
  6. If left <= right, read the left column from bottom to top. Then do left = left + 1.

Here is how the boundaries shrink after the outer lap is done.

top=0 bottom=2 left=0 right=2

Read top row 1 2 3 then top=1

Read right col 6 9 then right=1

Read bottom row 8 7 then bottom=1

Read left col 4 then left=1

Now top=1 bottom=1 left=1 right=1 read center 5

This Python version keeps four boundary variables and appends each cell in spiral order.

spiral.py
def spiral_order(matrix):
result = []
top, bottom = 0, len(matrix) - 1
left, right = 0, len(matrix[0]) - 1
while top <= bottom and left <= right:
for j in range(left, right + 1): # top row left to right
result.append(matrix[top][j])
top += 1
for i in range(top, bottom + 1): # right col top to bottom
result.append(matrix[i][right])
right -= 1
if top <= bottom: # bottom row right to left
for j in range(right, left - 1, -1):
result.append(matrix[bottom][j])
bottom -= 1
if left <= right: # left col bottom to top
for i in range(bottom, top - 1, -1):
result.append(matrix[i][left])
left += 1
return result
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(spiral_order(matrix))

The output of the above code will be:

[1, 2, 3, 6, 9, 8, 7, 4, 5]

Let us read the Python version line by line, because the boundary updates are the heart of it.

top, bottom = 0, len(matrix) - 1 and left, right = 0, len(matrix[0]) - 1 set the box around the whole matrix. top and left start at the first row and column. bottom and right start at the last row and column.

The while top <= bottom and left <= right loop runs while the box still has cells inside. When the box closes, both checks fail and we stop.

The first inner loop reads the top row. It goes from left to right and appends each cell. Right after, top += 1 shrinks the box from the top. That row is finished, so we never look at it again.

The second loop reads the right column from the new top down to bottom. Then right -= 1 shrinks the box from the right.

Now the guarded part. if top <= bottom checks that a bottom row still exists. Without this check, a single leftover middle row would get read twice. Inside, we read the bottom row from right back to left, then bottom -= 1.

if left <= right checks that a left column still exists. Same protection against double reading. Inside, we read the left column from bottom up to top, then left += 1.

So each lap reads the four edges and pulls all four boundaries inward. The center cell gets read on the final lap, when the box has shrunk to one cell.

⏱️ Time and Space Complexity

Both approaches read each cell exactly once. So the time is O(m Γ— n). The visited-grid version needs a full extra grid, so its space is O(m Γ— n). The boundary version needs only four numbers, so its extra space is O(1). The output list does not count as extra working space, since you must return it.

Approach Time Complexity Space Complexity
Visited grid O(m Γ— n) O(m Γ— n)
Four shrinking boundaries O(m Γ— n) O(1)

Tip

The two guard checks before reading the bottom row and the left column are easy to forget. Drop them and a single middle row or column gets read twice. Always add them.

🧩 Key Takeaways

  • βœ… Track four boundaries: top, bottom, left, and right. They box the unread region.
  • βœ… Read in four moves each lap: top row, right column, bottom row, left column.
  • βœ… Shrink the matching boundary right after each move.
  • βœ… Guard the bottom row and left column reads, so a lone middle row or column is not read twice.
  • βœ… The boundary method needs only O(1) extra space, far better than a visited grid.

Check Your Knowledge

4 questions Show quiz Hide quiz

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What order does spiral traversal follow on each lap?

    Why: Each lap reads the top row, then the right column, then the bottom row, then the left column.

  2. 2

    What do the four boundary variables represent?

    Why: top, bottom, left and right mark the edges of the unread region, and they shrink inward each lap.

  3. 3

    Why do we guard the bottom row and left column reads with extra checks?

    Why: When one row or column is left in the middle, the guard checks stop it from being read a second time.

  4. 4

    What is the extra space of the four-boundary approach?

    Why: It uses only four integer variables, so the extra working space is constant, O(1).

πŸš€ What’s Next?