ESC

Type to search the knowledge base.

Spiral Matrix

Traverse an m×n matrix in spiral order — shrink four boundaries, O(mn) time.

intermediate3 min read
  • dsa
  • arrays
  • interview
  • Google
  • Meta
  • Amazon

The problem

Given an m × n matrix, return all elements in spiral order (right, down, left, up, repeat).

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

Brute thoughts

Simulation with a visited matrix and direction vectors. Correct, O(mn) extra space for visited.

function spiralOrderVisited(matrix: number[][]): number[] {
  if (!matrix.length) return [];
  const rows = matrix.length, cols = matrix[0].length;
  const seen = Array.from({ length: rows }, () => Array(cols).fill(false));
  const dirs = [[0,1],[1,0],[0,-1],[-1,0]];
  const res: number[] = [];
  let r = 0, c = 0, d = 0;
  for (let i = 0; i < rows * cols; i++) {
    res.push(matrix[r][c]);
    seen[r][c] = true;
    const nr = r + dirs[d][0], nc = c + dirs[d][1];
    if (nr < 0 || nc < 0 || nr >= rows || nc >= cols || seen[nr][nc]) {
      d = (d + 1) % 4;
    }
    r += dirs[d][0];
    c += dirs[d][1];
  }
  return res;
}

Optimal: layer boundaries

function spiralOrder(matrix: number[][]): number[] {
  if (!matrix.length) return [];
  let top = 0;
  let bottom = matrix.length - 1;
  let left = 0;
  let right = matrix[0].length - 1;
  const res: number[] = [];

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

    for (let r = top; r <= bottom; r++) res.push(matrix[r][right]);
    right--;

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

    if (left <= right) {
      for (let r = bottom; r >= top; r--) res.push(matrix[r][left]);
      left++;
    }
  }
  return res;
}
Time O(m · n)
Space O(1) extra (not counting output)

The if (top <= bottom) / if (left <= right) guards stop double-counting the center row/column on non-square matrices.

Edge cases

  • Single row
  • Single column
  • 1×1
  • Wide short rectangles
  • Empty matrix

Common bugs

  • Missing the two guards → duplicate center
  • Off-by-one when shrinking bounds
  • Infinite loop if bounds never move

Interview delivery

  1. Four loops per layer.
  2. Shrink top/right/bottom/left.
  3. Guard single remaining row/col.
  4. Trace 3×3.
  5. Follow-up: generate spiral matrix I..n².

Single row / column guards

After the top row pass and right column pass, you may have already consumed the only remaining row or column. Without if (top <= bottom) before the bottom pass, you’d traverse the same row again backwards and duplicate values.

Pattern cousins

  • Spiral Matrix II: fill 1..n² in spiral.
  • Rotate image: different problem (in-place rotate).
  • Diagonal traverse: another boundary walk.

Spiral is pure loop index control — no fancy data structure.

Testing checklist

3×3, 1×n, n×1, 2×3, empty. Those five catch almost every bound bug.

Further reading