Matrix Spiral Traversal

AS
Ackshaey Singh
Difficulty Hard
Time Complexity
O(m * n)
Space Complexity
O(m * n)
Simulation Matrix Array
Adobe Zoho Amazon Google Microsoft Bloomberg Yahoo Accenture Meta Uber TCS Cisco Oracle Epic Systems Capital One Goldman Sachs DE Shaw Darwinbox Netflix MakeMyTrip Epic Games

You're midway through a Google onsite when the interviewer draws a 3x3 grid on the whiteboard and fills it with the numbers 1 through 9. "Read these off to me in spiral order," they say, tracing a finger clockwise from the top-left corner. It looks simple enough on paper, but turning that clockwise finger trace into code that handles rectangular matrices, single rows, and single columns without double-counting? That's where most candidates trip up. Spiral Matrix is a perennial favorite at companies like Adobe, Amazon, and Google because it tests whether you can translate a visual pattern into precise boundary management.

TL;DR

Use four boundary pointers - top, bottom, left, right - to define the current unvisited rectangle. In each iteration, traverse the top row left to right, the right column top to bottom, the bottom row right to left, and the left column bottom to top. After each traversal, shrink the corresponding boundary inward. Add boundary checks before the bottom row and left column to avoid double-counting on non-square matrices. This runs in O(m * n) time and uses O(1) auxiliary space beyond the output list.

Why This Problem Matters

Spiral Matrix is a pure simulation problem. There's no clever data structure trick or algorithmic insight that makes it faster - you simply need to walk the matrix in the right order without skipping or repeating cells. That sounds straightforward, but getting the boundary conditions right on the first try is harder than it looks. In practice, this is the kind of problem that separates candidates who plan before coding from those who dive in and debug for 20 minutes.

Understanding the Problem

Given an m x n matrix, return all its elements in spiral order - starting from the top-left corner, moving right along the top row, down the right column, left along the bottom row, up the left column, and then repeating for the inner layers.

Example 1 - a 3x3 matrix:

Input: matrix = [[1,2,3],[4,5,6],[7,8,9]]

Loading visualization...

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

Example 2 - a 3x4 rectangular matrix:

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

Loading visualization...

The outer ring (green) is visited first: 1, 2, 3, 4, 8, 12, 11, 10, 9, 5. Then the inner cells (orange): 6, 7.

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

Edge Cases to Consider

  1. Empty matrix: Return an empty list
  2. Single element: [[5]] returns [5]
  3. Single row: [[1,2,3,4,5]] returns [1,2,3,4,5]
  4. Single column: [[1],[2],[3]] returns [1,2,3]
  5. Non-square matrices: Must handle the inner boundary checks correctly

Solution Approach

Think about how you'd trace the spiral by hand. You start at the top-left and sweep right until you hit the edge, then turn downward, then sweep left along the bottom, then back up the left side. After completing one full loop around the perimeter, you move inward and repeat.

To implement this, define four boundaries:

  • top - the first row we haven't fully read
  • bottom - the last row we haven't fully read
  • left - the first column we haven't fully read
  • right - the last column we haven't fully read

Each iteration of the main loop peels off one outer layer:

  1. Top row (left to right): read matrix[top][left..right], then increment top
  2. Right column (top to bottom): read matrix[top..bottom][right], then decrement right
  3. Bottom row (right to left): read matrix[bottom][right..left], then decrement bottom
  4. Left column (bottom to top): read matrix[bottom..top][left], then increment left

Steps 3 and 4 need an extra check. After step 1 moves top down and step 2 moves right left, it's possible that top > bottom or left > right. Without the guard condition, you'd traverse a row or column that was already covered.

Tracing Through the 3x3 Example

Step 1 - Top row (left to right): Visit 1, 2, 3. Then top moves from 0 to 1.

Loading visualization...

Step 2 - Right column (top to bottom): Visit 6, 9. Then right moves from 2 to 1.

Loading visualization...

Step 3 - Bottom row (right to left): Check top <= bottom (1 <= 2, passes). Visit 8, 7. Then bottom moves from 2 to 1.

Loading visualization...

Step 4 - Left column (bottom to top): Check left <= right (0 <= 1, passes). Visit 4. Then left moves from 0 to 1.

Second iteration - Inner layer: top=1, bottom=1, left=1, right=1. Top row: visit 5. Then top becomes 2, which exceeds bottom (1), so the loop ends.

Loading visualization...

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

Boundary Evolution

Here's how the four pointers move through the 3x3 matrix:

Loading visualization...

Implementation

Prefer a different language? Jump to solutions in other languages.

import java.util.*;

public class Solution {

  public List<Integer> spiralOrder(int[][] matrix) {
    List<Integer> result = new ArrayList<>();
    if (matrix == null || matrix.length == 0) {
      return result;
    }

    int m = matrix.length;
    int n = matrix[0].length;
    int top = 0, bottom = m - 1, left = 0, right = n - 1;

    while (top <= bottom && left <= right) {
      // Traverse top row from left to right
      for (int i = left; i <= right; i++) {
        result.add(matrix[top][i]);
      }
      top++;

      // Traverse right column from top to bottom
      for (int i = top; i <= bottom; i++) {
        result.add(matrix[i][right]);
      }
      right--;

      // Traverse bottom row from right to left (if rows remain)
      if (top <= bottom) {
        for (int i = right; i >= left; i--) {
          result.add(matrix[bottom][i]);
        }
        bottom--;
      }

      // Traverse left column from bottom to top (if columns remain)
      if (left <= right) {
        for (int i = bottom; i >= top; i--) {
          result.add(matrix[i][left]);
        }
        left++;
      }
    }

    return result;
  }
}

Step-by-Step Breakdown

  1. Initialize boundaries: top = 0, bottom = m - 1, left = 0, right = n - 1. These define the current unvisited rectangle.

  2. Outer loop condition: Continue while top <= bottom && left <= right. Once the boundaries cross, every cell has been visited.

  3. Top row traversal: Iterate from left to right on row top, then move top down by one. This row is now consumed.

  4. Right column traversal: Iterate from the new top to bottom on column right, then move right left by one.

  5. Bottom row check and traversal: If top <= bottom still holds, iterate from the new right to left on row bottom, then move bottom up. The check prevents revisiting the top row when only one row remains.

  6. Left column check and traversal: If left <= right still holds, iterate from the new bottom to top on column left, then move left right. The check prevents revisiting the right column when only one column remains.

  7. Return: After the loop, result contains every element in spiral order.

Complexity Analysis

Time Complexity: O(m * n)

Every element is visited exactly once. The four inner loops together process all m * n cells, and each cell adds O(1) work (one array access and one list append). The outer while loop runs at most ceil(min(m, n) / 2) times, but the total work across all iterations is still proportional to the number of cells.

Space Complexity: O(m * n)

The output list stores all m * n elements. Beyond that, the algorithm uses only a fixed number of integer variables (top, bottom, left, right, loop counters), so the auxiliary space is O(1).

If the interviewer asks about space "not counting the output," the answer is O(1).

Common Pitfalls

  1. Missing boundary checks: Forgetting if (top <= bottom) before the bottom row traversal causes duplicates on matrices like [[1,2,3]] (single row) or [[1,2,3,4],[5,6,7,8]] (two rows). The top row traversal already covers all elements, and without the guard, the bottom row traversal would read them again.

  2. Wrong loop direction: The bottom row must go right to left (i--), and the left column must go bottom to top (i--). Reversing these produces incorrect output.

  3. Off-by-one on initialization: bottom should be m - 1, not m. Similarly for right. Starting at m causes an index-out-of-bounds on the first bottom row traversal.

  4. Not handling empty input: A null or zero-length matrix should return an empty list immediately. Skipping this check leads to a NullPointerException or ArrayIndexOutOfBoundsException on the first access.

Interview Tips

When working through this problem in an interview:

  1. Draw the matrix and trace the spiral path before writing any code. Mark each direction change. This makes the four-boundary approach feel natural rather than memorized.

  2. Start with the simplest case: a 3x3 matrix. Walk through one full layer, then check whether your boundaries are correct for the inner layer.

  3. Test with non-square input: try [[1,2,3,4]] (one row) and [[1],[2],[3]] (one column). These expose missing boundary checks.

  4. Mention the layer-by-layer perspective: telling the interviewer "each while-loop iteration peels off one concentric ring" shows you understand the structure of the algorithm, not just the code.

  5. Know the follow-ups: Spiral Matrix II (filling a matrix in spiral order) and Spiral Matrix III (starting from a position and spiraling outward) are common extensions. Both use the same boundary pointer technique.

Key Takeaways

  • The four-boundary technique (top, bottom, left, right) with inward shrinking is the standard approach for spiral traversal in 2026 interviews.
  • The two inner boundary checks (if top <= bottom before the bottom row, if left <= right before the left column) prevent double-counting on non-square matrices. This is the most common source of bugs.
  • Each full iteration of the while loop peels off one concentric layer of the matrix. The loop runs at most ceil(min(m, n) / 2) times, processing all m * n cells in total.
  • Test with single-row, single-column, and 1x1 matrices. These edge cases catch boundary errors that square matrices hide.
  • The same four-pointer pattern applies to Spiral Matrix II (filling) and Spiral Matrix III (outward spiral), making it a versatile technique worth internalizing.

Solutions in Other Languages

Python

from typing import List

class Solution:
    def spiral_order(self, matrix: List[List[int]]) -> List[int]:
        if not matrix or not matrix[0]:
            return []

        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):
                result.append(matrix[top][j])
            top += 1

            for i in range(top, bottom + 1):
                result.append(matrix[i][right])
            right -= 1

            if top <= bottom:
                for j in range(right, left - 1, -1):
                    result.append(matrix[bottom][j])
                bottom -= 1

            if left <= right:
                for i in range(bottom, top - 1, -1):
                    result.append(matrix[i][left])
                left += 1

        return result

JavaScript

class Solution {
  spiralOrder(matrix) {
    if (!matrix || matrix.length === 0) return [];

    const result = [];
    let top = 0;
    let bottom = matrix.length - 1;
    let left = 0;
    let right = matrix[0].length - 1;

    while (top <= bottom && left <= right) {
      for (let i = left; i <= right; i++) {
        result.push(matrix[top][i]);
      }
      top++;

      for (let i = top; i <= bottom; i++) {
        result.push(matrix[i][right]);
      }
      right--;

      if (top <= bottom) {
        for (let i = right; i >= left; i--) {
          result.push(matrix[bottom][i]);
        }
        bottom--;
      }

      if (left <= right) {
        for (let i = bottom; i >= top; i--) {
          result.push(matrix[i][left]);
        }
        left++;
      }
    }

    return result;
  }
}

TypeScript

class Solution {
  spiralOrder(matrix: number[][]): number[] {
    if (!matrix || matrix.length === 0) return [];

    const result: number[] = [];
    let top = 0;
    let bottom = matrix.length - 1;
    let left = 0;
    let right = matrix[0].length - 1;

    while (top <= bottom && left <= right) {
      for (let i = left; i <= right; i++) {
        result.push(matrix[top][i]);
      }
      top++;

      for (let i = top; i <= bottom; i++) {
        result.push(matrix[i][right]);
      }
      right--;

      if (top <= bottom) {
        for (let i = right; i >= left; i--) {
          result.push(matrix[bottom][i]);
        }
        bottom--;
      }

      if (left <= right) {
        for (let i = bottom; i >= top; i--) {
          result.push(matrix[i][left]);
        }
        left++;
      }
    }

    return result;
  }
}

C++

#include <vector>

class Solution {
public:
  std::vector<int> spiralOrder(std::vector<std::vector<int>> &matrix) {
    std::vector<int> result;
    if (matrix.empty()) return result;

    int m = matrix.size();
    int n = matrix[0].size();
    int top = 0, bottom = m - 1, left = 0, right = n - 1;

    while (top <= bottom && left <= right) {
      for (int j = left; j <= right; ++j) {
        result.push_back(matrix[top][j]);
      }
      ++top;

      for (int i = top; i <= bottom; ++i) {
        result.push_back(matrix[i][right]);
      }
      --right;

      if (top <= bottom) {
        for (int j = right; j >= left; --j) {
          result.push_back(matrix[bottom][j]);
        }
        --bottom;
      }

      if (left <= right) {
        for (int i = bottom; i >= top; --i) {
          result.push_back(matrix[i][left]);
        }
        ++left;
      }
    }

    return result;
  }
};

Go

func spiralOrder(matrix [][]int) []int {
    if len(matrix) == 0 {
        return []int{}
    }

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

    for top <= bottom && left <= right {
        for i := left; i <= right; i++ {
            result = append(result, matrix[top][i])
        }
        top++

        for i := top; i <= bottom; i++ {
            result = append(result, matrix[i][right])
        }
        right--

        if top <= bottom {
            for i := right; i >= left; i-- {
                result = append(result, matrix[bottom][i])
            }
            bottom--
        }

        if left <= right {
            for i := bottom; i >= top; i-- {
                result = append(result, matrix[i][left])
            }
            left++
        }
    }

    return result
}

Kotlin

class Solution {
    fun spiralOrder(matrix: Array<IntArray>): List<Int> {
        val result = mutableListOf<Int>()
        if (matrix.isEmpty() || matrix[0].isEmpty()) {
            return result
        }

        var top = 0
        var bottom = matrix.size - 1
        var left = 0
        var right = matrix[0].size - 1

        while (top <= bottom && left <= right) {
            for (i in left..right) {
                result.add(matrix[top][i])
            }
            top++

            for (i in top..bottom) {
                result.add(matrix[i][right])
            }
            right--

            if (top <= bottom) {
                for (i in right downTo left) {
                    result.add(matrix[bottom][i])
                }
                bottom--
            }

            if (left <= right) {
                for (i in bottom downTo top) {
                    result.add(matrix[i][left])
                }
                left++
            }
        }

        return result
    }
}

Swift

class Solution {
    func spiralOrder(_ matrix: [[Int]]) -> [Int] {
        var result: [Int] = []
        if matrix.isEmpty || matrix[0].isEmpty {
            return result
        }

        var top = 0
        var bottom = matrix.count - 1
        var left = 0
        var right = matrix[0].count - 1

        while top <= bottom && left <= right {
            for i in left...right {
                result.append(matrix[top][i])
            }
            top += 1

            if top <= bottom {
                for i in top...bottom {
                    result.append(matrix[i][right])
                }
            }
            right -= 1

            if top <= bottom {
                for i in stride(from: right, through: left, by: -1) {
                    result.append(matrix[bottom][i])
                }
                bottom -= 1
            }

            if left <= right {
                for i in stride(from: bottom, through: top, by: -1) {
                    result.append(matrix[i][left])
                }
                left += 1
            }
        }

        return result
    }
}

Scala

class Solution {
  def spiralOrder(matrix: Array[Array[Int]]): List[Int] = {
    if (matrix.isEmpty || matrix.head.isEmpty) return List()

    val m = matrix.length
    val n = matrix.head.length
    var top = 0
    var bottom = m - 1
    var left = 0
    var right = n - 1
    var result = List[Int]()

    while (top <= bottom && left <= right) {
      for (i <- left to right) {
        result = result :+ matrix(top)(i)
      }
      top += 1

      for (i <- top to bottom) {
        result = result :+ matrix(i)(right)
      }
      right -= 1

      if (top <= bottom) {
        for (i <- right to left by -1) {
          result = result :+ matrix(bottom)(i)
        }
        bottom -= 1
      }

      if (left <= right) {
        for (i <- bottom to top by -1) {
          result = result :+ matrix(i)(left)
        }
        left += 1
      }
    }

    result
  }
}

Ruby

class Solution
  def spiral_order(matrix)
    result = []
    return result if matrix.nil? || matrix.empty?

    m = matrix.length
    n = matrix[0].length
    top = 0
    bottom = m - 1
    left = 0
    right = n - 1

    while top <= bottom && left <= right
      (left..right).each { |i| result << matrix[top][i] }
      top += 1

      (top..bottom).each { |i| result << matrix[i][right] }
      right -= 1

      if top <= bottom
        right.downto(left).each { |i| result << matrix[bottom][i] }
        bottom -= 1
      end

      if left <= right
        bottom.downto(top).each { |i| result << matrix[i][left] }
        left += 1
      end
    end

    result
  end
end

Rust

impl Solution {
    pub fn spiral_order(matrix: Vec<Vec<i32>>) -> Vec<i32> {
        let mut result = Vec::new();
        if matrix.is_empty() || matrix[0].is_empty() {
            return result;
        }

        let m = matrix.len();
        let n = matrix[0].len();
        let mut top = 0i32;
        let mut bottom = m as i32 - 1;
        let mut left = 0i32;
        let mut right = n as i32 - 1;

        while top <= bottom && left <= right {
            for i in left..=right {
                result.push(matrix[top as usize][i as usize]);
            }
            top += 1;

            for i in top..=bottom {
                result.push(matrix[i as usize][right as usize]);
            }
            right -= 1;

            if top <= bottom {
                for i in (left..=right).rev() {
                    result.push(matrix[bottom as usize][i as usize]);
                }
                bottom -= 1;
            }

            if left <= right {
                for i in (top..=bottom).rev() {
                    result.push(matrix[i as usize][left as usize]);
                }
                left += 1;
            }
        }

        result
    }
}

C#

using System.Collections.Generic;

public class Solution {
    public List<int> SpiralOrder(int[][] matrix) {
        var result = new List<int>();
        if (matrix == null || matrix.Length == 0) {
            return result;
        }

        int m = matrix.Length;
        int n = matrix[0].Length;
        int top = 0, bottom = m - 1, left = 0, right = n - 1;

        while (top <= bottom && left <= right) {
            for (int i = left; i <= right; i++) {
                result.Add(matrix[top][i]);
            }
            top++;

            for (int i = top; i <= bottom; i++) {
                result.Add(matrix[i][right]);
            }
            right--;

            if (top <= bottom) {
                for (int i = right; i >= left; i--) {
                    result.Add(matrix[bottom][i]);
                }
                bottom--;
            }

            if (left <= right) {
                for (int i = bottom; i >= top; i--) {
                    result.Add(matrix[i][left]);
                }
                left++;
            }
        }

        return result;
    }
}

Dart

class Solution {
  List<int> spiralOrder(List<List<int>> matrix) {
    List<int> result = [];
    if (matrix.isEmpty || matrix[0].isEmpty) {
      return result;
    }

    int top = 0;
    int bottom = matrix.length - 1;
    int left = 0;
    int right = matrix[0].length - 1;

    while (top <= bottom && left <= right) {
      for (int i = left; i <= right; i++) {
        result.add(matrix[top][i]);
      }
      top++;

      for (int i = top; i <= bottom; i++) {
        result.add(matrix[i][right]);
      }
      right--;

      if (top <= bottom) {
        for (int i = right; i >= left; i--) {
          result.add(matrix[bottom][i]);
        }
        bottom--;
      }

      if (left <= right) {
        for (int i = bottom; i >= top; i--) {
          result.add(matrix[i][left]);
        }
        left++;
      }
    }

    return result;
  }
}

PHP

class Solution {
    public function spiralOrder(array $matrix): array {
        $result = [];
        if (empty($matrix) || empty($matrix[0])) {
            return $result;
        }

        $m = count($matrix);
        $n = count($matrix[0]);
        $top = 0;
        $bottom = $m - 1;
        $left = 0;
        $right = $n - 1;

        while ($top <= $bottom && $left <= $right) {
            for ($i = $left; $i <= $right; $i++) {
                $result[] = $matrix[$top][$i];
            }
            $top++;

            for ($i = $top; $i <= $bottom; $i++) {
                $result[] = $matrix[$i][$right];
            }
            $right--;

            if ($top <= $bottom) {
                for ($i = $right; $i >= $left; $i--) {
                    $result[] = $matrix[$bottom][$i];
                }
                $bottom--;
            }

            if ($left <= $right) {
                for ($i = $bottom; $i >= $top; $i--) {
                    $result[] = $matrix[$i][$left];
                }
                $left++;
            }
        }

        return $result;
    }
}

Practice Makes Perfect

Once you're comfortable with the four-boundary approach for Spiral Matrix, try these related problems to strengthen your matrix manipulation skills:

  • Spiral Matrix II: Fill an n x n matrix with values 1 to n^2 in spiral order
  • Rotate Image: Rotate a matrix 90 degrees in-place (uses layer-by-layer traversal)
  • Set Matrix Zeroes: Zero out rows and columns based on existing zeros

Remember, consistent practice is the key to succeeding in coding interviews. This problem and thousands of others are available on Firecode, where over 50,000 users have successfully prepared for technical interviews and landed six and seven-figure jobs at top tech companies. Whether you're just starting or preparing for your dream job, mastering fundamentals like this will set you up for success.

Happy coding, and may your spirals always converge!

Frequently Asked Questions

What is the four-boundary approach for spiral matrix traversal?
Maintain four pointers - top, bottom, left, right - representing the current unvisited boundaries of the matrix. In each iteration, traverse the top row left to right, the right column top to bottom, the bottom row right to left, and the left column bottom to top. After each traversal, shrink the corresponding boundary inward. Continue until the boundaries cross.
What is the time complexity of spiral matrix traversal?
O(m * n) where m is the number of rows and n is the number of columns. Every element in the matrix is visited exactly once during the spiral traversal. The four boundary pointers and inner loops together touch each cell one time.
What is the space complexity of spiral matrix traversal?
O(m * n) for the output list that stores all matrix elements. The algorithm itself only uses a constant number of variables (four boundary pointers and loop counters), so the auxiliary space is O(1). Some interviewers count only auxiliary space, in which case the answer is O(1).
Why do we need the extra boundary checks before traversing the bottom row and left column?
After traversing the top row and right column, the top and right boundaries are updated. For matrices with an odd number of rows or columns, these updates can cause the top pointer to exceed bottom or the left pointer to exceed right. Without the extra checks, the algorithm would double-count elements in the middle row or column.
How does spiral traversal handle a single-row or single-column matrix?
For a single row like [[1,2,3,4,5]], the algorithm traverses left to right on the top row, then increments top. Since top now exceeds bottom, the while loop terminates immediately without attempting the right column, bottom row, or left column. Single columns work symmetrically.
Can spiral matrix traversal be done recursively?
Yes, you can recursively peel off the outermost layer of the matrix, then recurse on the inner submatrix. Each recursive call processes one ring and passes the updated boundaries to the next call. The time complexity remains O(m * n), but the recursion adds O(min(m, n)) stack depth. The iterative approach with four pointers is generally preferred in interviews for its simplicity.
What is the difference between spiral order and layer-by-layer traversal?
They produce the same result. Layer-by-layer traversal explicitly processes each concentric rectangle from outermost to innermost. Spiral order follows the continuous path around the matrix. The four-boundary approach effectively implements layer-by-layer traversal - each full iteration of the while loop peels off one outer layer.
How do you generate a matrix in spiral order (the reverse problem)?
Use the same four-boundary technique but write values into the matrix instead of reading them. Start with an empty m x n matrix, maintain the same top/bottom/left/right pointers, and fill cells with incrementing values (1, 2, 3, ...) as you traverse the spiral path. This is sometimes called Spiral Matrix II.
How often does Spiral Matrix appear in coding interviews?
Spiral Matrix is one of the most frequently asked matrix problems in 2026 interviews, appearing regularly at Adobe, Amazon, Google, and Microsoft. It tests simulation skills, boundary management, and careful handling of edge cases - all qualities interviewers value.
What are common mistakes when implementing spiral matrix traversal?
The most frequent mistakes are forgetting the inner boundary checks (causing duplicates on non-square matrices), using the wrong loop direction for the bottom row or left column (forgetting to decrement), and off-by-one errors when initializing bottom and right to m-1 and n-1. Drawing the boundary pointers on paper before coding prevents most of these bugs.