Skip to content
← All posts

Spiral Matrix: Layer by Layer Traversal

8 min read
leetcodeproblemmediumarrays

You are given an m x n matrix of integers. Return all elements in spiral order. That means you start at the top-left corner, walk right across the top row, turn down the right column, go left across the bottom row, turn up the left column, and keep spiraling inward until every element is collected.

This is LeetCode 54: Spiral Matrix, and it is one of those problems that looks simple on the whiteboard but trips people up with off-by-one errors. The trick is to stop thinking about indices and start thinking about boundaries. Once you see the matrix as shrinking layers, the solution writes itself.

123456789101112top = 0bottom = 2left = 0right = 3
A 3x4 matrix with the spiral path. Blue = right, green = down, amber = left, red = up. Boundary variables define the current layer.

Why this problem matters

Spiral matrix traversal is a classic matrix traversal problem that interviewers love because it tests your ability to manage multiple moving pieces at once. Four boundaries, four directions, and a termination condition that depends on all of them. It is not algorithmically complex (no fancy data structures, no recursion), but it demands careful bookkeeping.

The pattern it teaches, boundary-layer traversal, shows up in problems like rotating a matrix in place, generating a spiral matrix, and printing a matrix in diagonal order. Any time you need to peel off the outer layer of a 2D structure and work inward, this is the tool you reach for.

The key insight

Think of the matrix as a series of concentric rectangular layers, like an onion. You peel the outer layer first (traverse all four sides of the outermost rectangle), then move inward to the next layer, and repeat until there is nothing left.

To track which layer you are on, maintain four boundary variables:

  • top: the topmost unvisited row
  • bottom: the bottommost unvisited row
  • left: the leftmost unvisited column
  • right: the rightmost unvisited column

Each full loop around a layer involves four traversals:

  1. Right across the top row, from left to right. Then move top down.
  2. Down the right column, from top to bottom. Then move right left.
  3. Left across the bottom row, from right to left. Then move bottom up.
  4. Up the left column, from bottom to top. Then move left right.

After each traversal, the boundary for that side moves inward by one. When the boundaries cross (top > bottom or left > right), you are done.

The solution

def spiral_order(matrix: list[list[int]]) -> list[int]:
    if not matrix:
        return []

    result = []
    top, bottom = 0, len(matrix) - 1
    left, right = 0, len(matrix[0]) - 1

    while top <= bottom and left <= right:
        # Traverse right across the top row
        for col in range(left, right + 1):
            result.append(matrix[top][col])
        top += 1

        # Traverse down the right column
        for row in range(top, bottom + 1):
            result.append(matrix[row][right])
        right -= 1

        # Traverse left across the bottom row (if rows remain)
        if top <= bottom:
            for col in range(right, left - 1, -1):
                result.append(matrix[bottom][col])
            bottom -= 1

        # Traverse up the left column (if columns remain)
        if left <= right:
            for row in range(bottom, top - 1, -1):
                result.append(matrix[row][left])
            left += 1

    return result

Let's break this down.

The four boundary variables start at the edges of the matrix. The while loop keeps going as long as there are unvisited rows (top <= bottom) and unvisited columns (left <= right).

Inside the loop, we do four traversals. After the first two (right and down), we check if top <= bottom before going left, and if left <= right before going up. These guards prevent double-counting the last row or column when the matrix has an odd dimension. Without them, a single remaining row would get traversed twice: once going right, then again going left.

The two if checks before the left and up traversals are the most common source of bugs. People forget them and get duplicate elements in the output. Whenever you see a spiral matrix solution producing too many elements, those missing guards are almost always the reason.

Visual walkthrough

Let's trace through a 3x4 matrix step by step. Watch how the boundaries shrink after each traversal.

Step 1: Traverse top row, left to right.

top=0, bottom=2, left=0, right=3

123456789101112

Collect [1, 2, 3, 4]. Then top += 1.

Step 2: Traverse right column, top to bottom.

top=1, bottom=2, left=0, right=3

123456789101112

Collect [8, 12]. Then right -= 1.

Step 3: Traverse bottom row, right to left.

top=1, bottom=2, left=0, right=2

123456789101112

Collect [11, 10, 9]. Then bottom -= 1.

Step 4: Traverse left column, bottom to top.

top=1, bottom=1, left=0, right=2

123456789101112

Collect [5]. Then left += 1.

Step 5: New layer. Traverse top row again.

top=1, bottom=1, left=1, right=2

123456789101112

Collect [6, 7]. Then top += 1. Now top > bottom, so we stop.

Step 6: All cells collected. Done!

top=2, bottom=1, left=1, right=2

123456789101112

Result: [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]

Notice how each step handles one side of the current layer, then moves the corresponding boundary inward. After the outer layer is fully traversed (steps 1 through 4), the boundaries have shrunk to just the inner cells. Step 5 collects the remaining inner row, and then the boundaries cross, ending the loop.

Why the boundary checks matter

Consider a 1x4 matrix: [[1, 2, 3, 4]]. Here is what happens:

  1. Right: traverse [1, 2, 3, 4]. Then top becomes 1.
  2. Down: range(1, 1) is empty. Nothing to do. Then right becomes 2.
  3. Left: top (1) > bottom (0). Skip.
  4. Up: left (0) <= right (2). But range(0, 1, -1) is empty anyway.

Without the if top <= bottom check in step 3, we would traverse the bottom row again in reverse and output [1, 2, 3, 4, 3, 2, 1]. The guard correctly prevents this.

The same logic applies to a 4x1 column matrix. Without the if left <= right check in step 4, we would go back up a column we already traversed.

When the remaining unvisited area is a single row, only the "right" traversal runs. When it is a single column, only "right" (one cell) and "down" run. The boundary checks ensure we never retrace our steps in these degenerate cases.

Complexity analysis

MetricValue
TimeO(m * n)
SpaceO(1) extra (output excluded)

Time: every cell is visited exactly once. The four for loops across all iterations of the while loop collectively cover all m * n cells.

Space: the result list holds m * n elements, but that is the required output, not extra space. The only additional variables are the four boundaries and the loop counters, which is O(1).

Edge cases

Before submitting, make sure your solution handles these:

  • Empty matrix: [] should return []. The if not matrix check covers this.
  • Single element: [[5]] should return [5]. One pass through the right traversal, then all boundaries cross.
  • Single row: [[1, 2, 3]]. Only the right traversal runs. The left traversal is skipped by the top <= bottom guard.
  • Single column: [[1], [2], [3]]. Right traversal gets one element, down traversal gets the rest. Left and up are skipped.
  • Square matrix: works the same as rectangular. The layers are concentric squares instead of rectangles.

The building blocks

This problem is built on one reusable piece that CodeBricks drills independently:

Boundary-layer traversal

The pattern of maintaining four boundary variables (top, bottom, left, right) and peeling off one rectangular layer at a time:

top, bottom = 0, len(matrix) - 1
left, right = 0, len(matrix[0]) - 1

while top <= bottom and left <= right:
    # process the current layer
    # ... traverse all four sides ...
    # shrink boundaries inward
    top += 1
    bottom -= 1
    left += 1
    right -= 1

This skeleton is the foundation for any problem that processes a matrix from the outside in. The specific logic inside the loop changes depending on the problem, but the boundary management stays the same:

  • Spiral Matrix II (LeetCode 59): instead of reading values in spiral order, you write values 1 through n*n in spiral order. Same four boundaries, same four directions, but you assign instead of append.
  • Rotate Image (LeetCode 48): rotate a matrix 90 degrees in place. You process each layer by rotating the four corner elements in a cycle, then move to the next layer.
  • Matrix Layer Rotation (various problems): shift elements along the perimeter of each layer. Same boundary structure, different operation per side.

The key thing to internalize is the four-phase loop: right, down, left, up. Each phase uses the current boundary values. Each phase advances one boundary. The boundary checks before left and up prevent double-counting on thin layers. Once this pattern is automatic, spiral traversal is just filling in the details.

Common mistakes

1. Forgetting the boundary checks before left and up traversals. This causes duplicate elements for matrices where one dimension is odd. Always check if top <= bottom before going left and if left <= right before going up.

2. Using the wrong range direction. The left traversal goes from right to left, so you need range(right, left - 1, -1). The up traversal goes from bottom to top, so you need range(bottom, top - 1, -1). Getting the - 1 wrong means you skip the first or last element.

3. Updating boundaries at the wrong time. Each boundary must be updated after its traversal, not before. If you increment top before traversing the top row, you skip that row entirely.

4. Off-by-one in the while condition. The loop should be while top <= bottom and left <= right, not <. Using strict less-than would skip the final single row or column.

When to reach for this pattern

Look for these signals:

  • The problem involves a 2D matrix and mentions "spiral," "layer," or "ring"
  • You need to process a matrix from the outside inward (or inside outward)
  • The problem asks you to traverse or modify elements along the perimeter of a sub-matrix
  • Rotating a matrix in place layer by layer

If you see any of these, set up the four boundaries and start peeling layers. The boundary-layer skeleton handles the structural complexity, and you just plug in the per-cell logic.

From understanding to recall

You have read through the solution. You understand why the four boundaries work and why the guard checks prevent double-counting. But can you type this from scratch when you are staring at a blank editor during an interview?

The boundary-layer traversal pattern has a few moving parts that are easy to mix up: which boundary moves after which direction, whether to use <= or <, the reverse range syntax, and the two conditional checks. None of these are hard ideas. They are recall details that slip away under pressure.

Spaced repetition fixes this. You practice writing the boundary setup, the four traversals, and the guard checks from memory. You do it today, again in two days, again in five days. After a few reps, the whole pattern flows out automatically. You see "spiral order" in a problem statement, and the four-boundary loop appears on your screen without hesitation.

The boundary-layer traversal is one of roughly 60 reusable building blocks that cover hundreds of LeetCode problems. Learning these individually and drilling them with spaced repetition is far more effective than grinding random problems and hoping the patterns stick.

Related posts

  • Rotate Array - Another problem where the trick is managing boundaries and directions carefully in an array transformation
  • Number of Islands - Grid traversal using DFS, the other essential technique for 2D matrix problems
  • Trapping Rain Water - Boundary tracking from both sides, a similar "converging boundaries" idea in 1D

CodeBricks breaks Spiral Matrix into its boundary-layer-traversal building block, then drills it independently with spaced repetition. You type the four-boundary setup, the four traversals, and the guard checks from scratch until the pattern is automatic. When a spiral or layer problem shows up in your interview, you do not think about it. You just write it.