Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Return every element of an m×n matrix in clockwise spiral order. The clean simulation walks with a direction vector plus a visited set (O(mn) space). The canonical interview answer keeps four boundary pointers — top, bottom, left, right — and peels one edge at a time, shrinking the boundary inward, using O(1) extra space.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you can manage several moving boundaries at once without double-counting cells, and whether you spot that the leftover middle row/column needs an explicit guard.
Where candidates slip
Omitting the `if top <= bottom` / `if left <= right` guards before the bottom-row and left-column walks, so a single remaining row or column gets emitted twice on odd-sized matrices.
How to stand out
State up front that the answer has exactly m·n elements and that the only subtlety is the guards on the final two edges — then explain why a non-square middle strip is where the double-count happens.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewTraverse m×n grid in spiral orderExtra space OK?YesNo / O(1)Explore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Direction Vector + Visited Set
1Hint 1Explore hintHide hint
Think of a turtle walking the grid. It goes straight until it would step off the edge or onto a cell it already visited — then it turns right.
2Hint 2Explore hintHide hint
Encode the four directions as (dr, dc) vectors in clockwise order: right (0,1), down (1,0), left (0,-1), up (-1,0). Cycle through them with `dir = (dir + 1) % 4` on each turn.
3Hint 3Explore hintHide hint
Keep a visited[m][n] grid. Take m·n steps; before each move, if the next cell is out of bounds or visited, rotate the direction. Record and move.
A · Core idea
Simulate a walker that always goes straight until blocked, then turns right. 'Blocked' means the next cell is either off the grid or already visited.
Why a visited grid makes this easy to get right. The hard part of a spiral is knowing when to turn. With a visited[][] marker the rule is dead simple: try to step forward; if that cell is out of bounds or already marked, rotate the direction clockwise and step instead. You never have to reason about where the boundary is — the visited cells form it for you, and the turn rule is uniform for all four sides.
Why the direction vectors cycle cleanly. Listing the moves in clockwise order — right, down, left, up — means a single dir = (dir + 1) % 4 performs every turn. The walker takes exactly m·n steps; on each, it either continues or makes one right turn, so the loop is bounded and terminates precisely when the grid is full.
Why we don't ship it. It allocates an m×n visited grid — O(m·n) extra space for a problem that has an O(1) solution. It's a great way to explain the spiral, but the boundary-pointer version encodes the same turning logic in four integers instead of a whole grid.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
matrix = [[1,2,3],[4,5,6],[7,8,9]]Array state
outer ring
1,2,3,6,9,8,7,4 collected clockwise
[1,2,3,6,9,8,7,4,5]C · Code with teaching comments
Reference implementation previewpublic List<Integer> spiralOrder(int[][] matrix) { int m = matrix.length, n = matrix[0].length; boolean[][] seen = new boolean[m][n]; int[][] dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; List<Integer> out = new ArrayList<>();Explore the full codeHide the full code
D · Why it works and complexityO(m·n) timeO(m·n) spaceOpen the invariant and proofExplore the proofHide the proof
The visited set is doing one job: telling the walker where the wall is. Once you notice the walls are always four straight lines forming a shrinking rectangle, you can replace the whole grid with four numbers — that realisation is the bridge to the optimal solution.
ComplexityO(m·n) time, O(m·n) space — Exactly m·n cells are emitted, each with O(1) work and at most one turn → O(m·n) time. The visited grid is m·n booleans → O(m·n) space.
E · Edge cases and pitfalls5matrix = [[7]] — m·n=1: emit matrix[0][0]; the loop's single iteration ends before any turn.Explore edge casesHide edge cases
Edge cases handled
matrix = [[7]]m·n=1: emit matrix[0][0]; the loop's single iteration ends before any turn.matrix = [[1,2,3]]Single row: walker goes right, would turn down but (1,*) is off-grid, so it stops after 1,2,3.
Pitfalls
- Reading n from matrix.length instead of matrix[0].length — swaps rows and columns on rectangles.
- Forgetting the visited check (only testing bounds) — the walker loops the outer ring forever.
- Allocating the m×n visited grid when the O(1) boundary version is available.
Approach 2 of 2 · Optimal
Four Boundary Pointers (Optimal)
1Hint 1Explore hintHide hint
Hold the un-emitted region as a rectangle defined by four lines: top, bottom, left, right. Each spiral step consumes one full edge of that rectangle.
2Hint 2Explore hintHide hint
Walk the top row left→right (then top++), the right column top→bottom (then right--), the bottom row right→left (then bottom--), the left column bottom→top (then left++). Repeat while top≤bottom and left≤right.
3Hint 3Explore hintHide hint
Before the bottom-row and left-column walks, guard with `if top <= bottom` and `if left <= right` — on odd dimensions a single strip remains and would otherwise be emitted twice.
A · Core idea
Track the un-visited region as a rectangle with four boundaries and peel one edge per move, contracting the matching boundary inward after each.
Why four pointers replace the whole visited grid. The unvisited cells always form a rectangle, and a rectangle is fully described by four numbers: top, bottom, left, right. Each spiral step walks one complete edge of that rectangle, then moves its boundary one step inward (top++ after the top row, right-- after the right column, and so on). The boundaries themselves encode 'where have I been', so the O(m·n) visited grid collapses to O(1) state.
Why the bottom row and left column need guards. After peeling the top row and right column, you might be left with a single remaining row (when top == bottom) or a single remaining column (when left == right). The first two edge-walks already consumed those cells. Walking the bottom row again when top > bottom, or the left column when left > right, re-emits cells you've already output. The guards if top <= bottom and if left <= right are exactly the check that says 'there's still an unconsumed row/column here'. This is the one subtlety the problem is really testing.
Why it's optimal. You still touch each of the m·n cells once — you can't do better, every element must be emitted — but you carry only four integers of state. Same time, minimal space, and the structure generalises directly to Spiral Matrix II (fill instead of read) and ring-by-ring problems.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
matrix = [[1,2,3],[4,5,6],[7,8,9]]Array state
ring 0
top=0,bottom=2,left=0,right=2 — peel outer 8 cells
[1,2,3,6,9,8,7,4,5]C · Code with teaching comments
D · Why it works and complexityO(m·n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
The leap from the previous approach is recognising the visited region is always a rectangle, so four numbers fully describe it. Representing a moving frontier by its boundaries rather than by marking every visited cell is a recurring space-saving trick — the same instinct that turns an O(n) seen-set into two pointers in sliding-window problems.
ComplexityO(m·n) time, O(1) space — Each cell is appended exactly once across the four edge-walks → O(m·n) time. Only four integer pointers and the output list (which is required, not auxiliary) → O(1) extra space.
E · Edge cases and pitfalls5matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]] — 3×4: outer ring then inner row 6,7 — the bottom-row guard prevents re-walking row 1 after right shrinks.Explore edge casesHide edge cases
Edge cases handled
matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]3×4: outer ring then inner row 6,7 — the bottom-row guard prevents re-walking row 1 after right shrinks.matrix = [[1],[2],[3]]3×1 column: top row emits 1; right column emits 2,3; both guards fail (top>bottom, left>right) so nothing repeats.
Pitfalls
- Dropping the `if top <= bottom` / `if left <= right` guards — double-emits the middle row/column on odd dimensions.
- Using m for columns or n for rows — breaks immediately on rectangular input.
- Forgetting to update a boundary (e.g. no top++) — infinite loop, never terminates.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic List<Integer> spiralOrder(int[][] matrix) { int m = matrix.length, n = matrix[0].length; int top = 0, bottom = m - 1, left = 0, right = n - 1; List<Integer> out = new ArrayList<>(); while (top <= bottom && left <= right) {Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Missing the bottom/left guards
When only one row or column remains, the top-row and right-column walks already consumed it. Walking the bottom row / left column again re-emits those cells — your output is longer than m·n.
Explore the code comparisonHide the code comparison
Missing the bottom/left guards
When only one row or column remains, the top-row and right-column walks already consumed it. Walking the bottom row / left column again re-emits those cells — your output is longer than m·n.
Don't write this
while top <= bottom and left <= right:
for c in range(left, right + 1): out.append(matrix[top][c])
top += 1
for r in range(top, bottom + 1): out.append(matrix[r][right])
right -= 1
for c in range(right, left - 1, -1): out.append(matrix[bottom][c]) # no guard!
bottom -= 1
for r in range(bottom, top - 1, -1): out.append(matrix[r][left]) # no guard!
left += 1Write this instead
while top <= bottom and left <= right:
for c in range(left, right + 1): out.append(matrix[top][c])
top += 1
for r in range(top, bottom + 1): out.append(matrix[r][right])
right -= 1
if top <= bottom:
for c in range(right, left - 1, -1): out.append(matrix[bottom][c])
bottom -= 1
if left <= right:
for r in range(bottom, top - 1, -1): out.append(matrix[r][left])
left += 12Confusing rows and columns
On a rectangular matrix, matrix.length is the row count (m) and matrix[0].length is the column count (n). Using the wrong one for the boundary indices walks off the grid or stops early.
Explore the code comparisonHide the code comparison
Confusing rows and columns
On a rectangular matrix, matrix.length is the row count (m) and matrix[0].length is the column count (n). Using the wrong one for the boundary indices walks off the grid or stops early.
Don't write this
int m = matrix[0].length; // wrong: this is the column count
int n = matrix.length; // wrong: this is the row count
int top = 0, bottom = m - 1, left = 0, right = n - 1;Write this instead
int m = matrix.length; // rows
int n = matrix[0].length; // columns
int top = 0, bottom = m - 1, left = 0, right = n - 1;3Not advancing a boundary
Each edge-walk must be followed by shrinking its boundary (top++, right--, bottom--, left++). Skip one and the while condition never tightens — the loop spins forever or re-reads the same edge.
Explore the code comparisonHide the code comparison
Not advancing a boundary
Each edge-walk must be followed by shrinking its boundary (top++, right--, bottom--, left++). Skip one and the while condition never tightens — the loop spins forever or re-reads the same edge.
Don't write this
for (int c = left; c <= right; c++) out.add(matrix[top][c]);
// forgot top++ → top row re-walked next iterationWrite this instead
for (int c = left; c <= right; c++) out.add(matrix[top][c]);
top++;Revise
Carry the pattern into the interview
Pattern to remember
Boundary-shrinking spiral traversal (four pointers)
Code pattern to memorize
top, bottom = 0, m-1 ; left, right = 0, n-1
while top<=bottom and left<=right:
walk top row L→R, top++
walk right col T→B, right--
if top<=bottom: walk bottom row R→L, bottom--
if left<=right: walk left col B→T, left++Rules to remember
- 1Use four boundary pointers (top/bottom/left/right) and peel one full edge per step, then move that boundary inward.
- 2Guard the bottom row and left column with
if top <= bottom/if left <= right— without them a single leftover row or column is traversed twice. - 3Loop condition is
top <= bottom AND left <= right— the spiral ends the instant the boundaries cross. - 4It's m×n, not necessarily square — track rows (m) and columns (n) separately; mixing them up is the top bug.
- 5Append exactly m·n elements total. If your output length isn't m·n, a boundary update or a guard is off by one.
Use this pattern when
- Traverse a grid in a non-row-major order (spiral, diagonal, zigzag)
- Output count is fixed (m·n) and you're peeling layers inward
- Problem describes 'rings', 'layers', or 'clockwise/counter-clockwise' walks
- You need O(1) extra space on a grid walk
Choose another pattern when
- Plain row-by-row or column-by-column scan → just nest two loops, no spiral machinery
- You need shortest path / connectivity on the grid → that's BFS/DFS, not boundary peeling
- Rotation or reflection by a fixed angle → transpose + reverse (Rotate Image)
- Random access to the k-th spiral element → derive a formula, don't simulate the whole walk
Interview takeaway
A spiral is just four directed edge-walks repeated on a shrinking rectangle. Hold the rectangle in four pointers, peel one edge at a time, and contract the matching boundary — the same boundary-as-state idea powers Spiral Matrix II, diagonal traversals, and any 'peel the outer layer' grid problem.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternCanonical Boundary-Shrinking Grid Traversal. Recognise it whenever you peel a grid layer by layer or walk it in a fixed non-row-major order — represent the frontier with four boundary pointers. Same family as Spiral Matrix II, Rotate Image, and Diagonal Traverse.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Spiral Matrix IIComing soonGenerate an n×n matrix filled 1..n² in spiral order — same boundary peeling, but write instead of read.
- Spiral Matrix IIIComing soonSpiral that starts off-center and may leave the grid — use growing step lengths instead of fixed boundaries.
- Rotate ImageAnother coordinate-walking matrix problem — in-place transpose + reverse.
- Diagonal TraverseComing soonSame 'walk a grid in a non-row-major order' idea, with diagonal direction switching.