TechByteByByte

Print a Matrix in Spiral Order - Java

A difficult QA/automation coding interview question: print a Matrix in Spiral Order, with a full Java walkthrough, dry run, and common interviewer follow-ups.

#Arrays#2D Arrays#Matrix Traversal#Difficult#Java

@Note: This is categorized as โ€œDifficultโ€ because it is a complex 2D array traversal index problem with multiple indices that can easily lead to IndexOutOfBoundsException bugs.


Category: Difficult | Concepts used: Boundary tracking, 2D array traversal


Problem Statement

Given a 2D matrix, print all its elements in spiral order (starting from the top-left, going right, then down, then left, then up, spiraling inward).

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

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

Examples (with edge scenarios)

#Input MatrixOutputWhy
13x3 square matrix (above)[1,2,3,6,9,8,7,4,5]Standard spiral
21x1 matrix [[5]][5]Single element โ€” trivial spiral
31xN matrix [[1,2,3,4]] (single row)[1,2,3,4]No โ€œdownโ€ or โ€œupโ€ movement needed, just left-to-right
4Nx1 matrix (single column)Top to bottom, straight downNo โ€œrightโ€ or โ€œleftโ€ movement needed
5Non-square (rectangular) matrix, e.g., 2x3Still works, just uneven spiral shapeMust handle rows โ‰  columns correctly

Common Fresher Mistake

MistakeWhat happensFix
Not updating boundary variables correctly after each directionElements get printed twice, or missed entirely, or IndexOutOfBoundsExceptionCarefully shrink the boundaries (top++, bottom--, left++, right--) after completing each of the 4 directional sweeps
Not checking boundary validity before the last โ€œupโ€ and โ€œleftโ€ sweeps in a partially-completed final ringFor single-row or single-column matrices, printing the same row/column twiceAdd boundary condition checks (top <= bottom, left <= right) before each directional sweep

Before You Code: Clarify the Contract

Before choosing an algorithm, confirm whether the matrix may be empty, whether every row has the same length, and whether traversal may modify the matrix. The solution below assumes a rectangular matrix and returns values without changing the input.

Analogy: Mowing the Lawn Inward

Imagine you are a lawnmower operator mowing a square field, and you want to mow the grass in a spiral pattern starting from the outer boundary and working inward:

  • You have four fence lines marking your boundaries: top (0), bottom (2), left (0), and right (2).
  • Mowing Loop:
    • Drive East: You mow along the top boundary from left to right. Once done, you move the top fence down (top++) since that row is completely cleared.
    • Drive South: You mow down the right boundary from top to bottom. Once done, you move the right fence left (right--).
    • Drive West: If you still have rows left to mow (top <= bottom), you mow along the bottom boundary from right to left. Once done, you move the bottom fence up (bottom--).
    • Drive North: If you still have columns left to mow (left <= right), you mow up the left boundary from bottom to top. Once done, you move the left fence right (left++).
  • You repeat this circular route. Each time, your yard shrinks, until the fences meet and there is no grass left to mow!

Solution 1 โ€” Boundary Tracking (Four Direction Pointers)

Intuition

Picture the matrix as having four shrinking โ€œwallsโ€: top, bottom, left, right. Traverse along each wall in order โ€” right across the top row, down the right column, left across the bottom row, up the left column โ€” then shrink the corresponding boundary inward after each sweep (since that outer layer is now fully visited). Repeat this shrinking process until the boundaries cross, meaning every element has been visited.

import java.util.ArrayList;
import java.util.List;

public class SpiralMatrix {
    public static List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> result = new ArrayList<>();
        if (matrix.length == 0) return result;

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

while (top <= bottom && left <= right) {
            // 1. Traverse top row, left to right
            for (int col = left; col <= right; col++) {
                result.add(matrix[top][col]);
            }
            top++; // top row done, shrink boundary

// 2. Traverse right column, top to bottom
            for (int row = top; row <= bottom; row++) {
                result.add(matrix[row][right]);
            }
            right--; // right column done, shrink boundary

// 3. Traverse bottom row, right to left (only if a row remains)
            if (top <= bottom) {
                for (int col = right; col >= left; col--) {
                    result.add(matrix[bottom][col]);
                }
                bottom--;
            }

// 4. Traverse left column, bottom to top (only if a column remains)
            if (left <= right) {
                for (int row = bottom; row >= top; row--) {
                    result.add(matrix[row][left]);
                }
                left++;
            }
        }
        return result;
    }

public static void main(String[] args) {
        int[][] matrix = {
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}
        };
        System.out.println(spiralOrder(matrix)); // [1, 2, 3, 6, 9, 8, 7, 4, 5]

int[][] singleRow = {{1, 2, 3, 4}};
        System.out.println(spiralOrder(singleRow)); // [1, 2, 3, 4]
    }
}

Output:

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

Dry Run (3x3 matrix: [[1,2,3],[4,5,6],[7,8,9]])

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

Loop iteration 1:
  Step 1 (top row, left->right): add 1,2,3 -> result=[1,2,3] -> top=1
  Step 2 (right col, top->bottom): add 6,9 -> result=[1,2,3,6,9] -> right=1
  Step 3 (bottom row, right->left) [top<=bottom, 1<=2 true]: add 8,7 -> result=[1,2,3,6,9,8,7] -> bottom=1
  Step 4 (left col, bottom->top) [left<=right, 0<=1 true]: add 4 -> result=[1,2,3,6,9,8,7,4] -> left=1

Loop check: top=1<=bottom=1 AND left=1<=right=1 -> continue

Loop iteration 2:
  Step 1 (top row=1, left=1 to right=1): add matrix[1][1]=5 -> result=[...,5] -> top=2
  Step 2 (right col, top=2 to bottom=1): loop doesn't execute (2>1) -> right=0
  Step 3: top<=bottom? 2<=1 false -> skip
  Step 4: left<=right? 1<=0 false -> skip

Loop check: top=2<=bottom=1? false -> loop ends

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

Interviewerโ€™s take

This is the standard, expected solution for spiral matrix traversal โ€” the boundary-shrinking technique (top/bottom/left/right pointers) is the well-known, correct approach. The tricky part (and what separates a working solution from a buggy one) is the conditional checks before steps 3 and 4 (if (top <= bottom) and if (left <= right)) โ€” without these, single-row or single-column matrices get elements printed twice.

Follow-up questions you might get:

  • โ€œWhy are the checks before steps 3 and 4 necessary, but not before steps 1 and 2?โ€ โ†’ By the time we reach steps 3 and 4, the boundaries may have already crossed (e.g., after processing a single remaining row in step 1, top might now exceed bottom) โ€” without checking, step 3 would incorrectly re-print the same row that step 1 just handled.
  • โ€œWhatโ€™s the time and space complexity?โ€ โ†’ O(rows ร— cols) time (every element visited exactly once), O(rows ร— cols) space for the result list (or O(1) extra if just printing without storing).
  • โ€œHow would you handle an empty matrix, or a matrix with empty rows?โ€ โ†’ Explicitly check matrix.length == 0 (and potentially matrix[0].length == 0) at the start and return an empty result immediately.

๐Ÿ“Š Visual Flowchart

graph TD
    Start["Input Matrix"] --> Init["top=0, bottom=rows-1<br>left=0, right=cols-1"]
    Init --> Loop{"top <= bottom AND left <= right?"}
    Loop -->|Yes| Step1["1. Traverse top row (col: left to right)<br>top++"]
    Step1 --> Step2["2. Traverse right column (row: top to bottom)<br>right--"]
    Step2 --> Guard3{"top <= bottom?"}
    Guard3 -->|Yes| Step3["3. Traverse bottom row (col: right to left)<br>bottom--"]
    Guard3 -->|No| Guard4
    Step3 --> Guard4{"left <= right?"}
    Guard4 -->|Yes| Step4["4. Traverse left column (row: bottom to top)<br>left++"]
    Guard4 -->|No| Loop
    Step4 --> Loop
    Loop -->|No| End["Return result List"]

Final Verdict โ€” Which Solution Should You Give?

  • Solution 1 (boundary tracking) is the standard, essentially only reasonable approach for this classic problem โ€” the key skill being tested is careful boundary management and dry-running through edge cases (single row, single column) to make sure the conditional guards are correctly placed.
  • Walking through a non-square example (like a 2x4 matrix) during the interview is a great way to demonstrate the boundary logic holds up beyond the โ€œniceโ€ square case.

Quick Recap

ApproachTimeSpaceInterview Signal
Boundary tracking (4 pointers)O(rows ร— cols)O(rows ร— cols) for resultStandard โ€” careful edge-case handling is the key skill
Author
TechByteByByte Editorial Team
Reviewed by
TechByteByByte Admin
Published
Last reviewed