Matrix Spiral Traversal
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
- Empty matrix: Return an empty list
- Single element:
[[5]]returns[5] - Single row:
[[1,2,3,4,5]]returns[1,2,3,4,5] - Single column:
[[1],[2],[3]]returns[1,2,3] - 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 readbottom- the last row we haven't fully readleft- the first column we haven't fully readright- the last column we haven't fully read
Each iteration of the main loop peels off one outer layer:
- Top row (left to right): read
matrix[top][left..right], then incrementtop - Right column (top to bottom): read
matrix[top..bottom][right], then decrementright - Bottom row (right to left): read
matrix[bottom][right..left], then decrementbottom - Left column (bottom to top): read
matrix[bottom..top][left], then incrementleft
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
-
Initialize boundaries:
top = 0,bottom = m - 1,left = 0,right = n - 1. These define the current unvisited rectangle. -
Outer loop condition: Continue while
top <= bottom && left <= right. Once the boundaries cross, every cell has been visited. -
Top row traversal: Iterate from
lefttorighton rowtop, then movetopdown by one. This row is now consumed. -
Right column traversal: Iterate from the new
toptobottomon columnright, then moverightleft by one. -
Bottom row check and traversal: If
top <= bottomstill holds, iterate from the newrighttolefton rowbottom, then movebottomup. The check prevents revisiting the top row when only one row remains. -
Left column check and traversal: If
left <= rightstill holds, iterate from the newbottomtotopon columnleft, then moveleftright. The check prevents revisiting the right column when only one column remains. -
Return: After the loop,
resultcontains 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
-
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. -
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. -
Off-by-one on initialization:
bottomshould bem - 1, notm. Similarly forright. Starting atmcauses an index-out-of-bounds on the first bottom row traversal. -
Not handling empty input: A null or zero-length matrix should return an empty list immediately. Skipping this check leads to a
NullPointerExceptionorArrayIndexOutOfBoundsExceptionon the first access.
Interview Tips
When working through this problem in an interview:
-
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.
-
Start with the simplest case: a 3x3 matrix. Walk through one full layer, then check whether your boundaries are correct for the inner layer.
-
Test with non-square input: try
[[1,2,3,4]](one row) and[[1],[2],[3]](one column). These expose missing boundary checks. -
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.
-
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 <= bottombefore the bottom row,if left <= rightbefore 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 allm * ncells 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 nmatrix 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!