Spiral Matrix II: Mastering the Layer-by-Layer Strategy for Matrix Generation
Decode the elegant layer-by-layer approach to generating spiral matrices. Learn how to think in concentric rings and avoid complex edge cases with clean, scalable code.
Generating a spiral matrix might look like magic when you first see the solution code. Rows and columns fill in perfect loops, numbers snake around the edges, and everything lines up neatly. But the brilliance of this problem is not in complex algorithms—it's in organizing the work layer by layer.
In this post, we'll break down the thought process behind the Spiral Matrix II problem, explain why concepts like loops, offsets, and rings exist, and walk through the solution step by step.
Problem Statement
We're asked to generate an n × n
matrix filled with numbers 1
to n²
in spiral order.
For example, for n = 3
, the result should be:
[
[1, 2, 3],
[8, 9, 4],
[7, 6, 5]
]
Step 1: Key Observations
1. The spiral pattern can be seen as concentric "rings" (or layers)
- The outermost ring: top row, right column, bottom row, left column
- Then, move inward and repeat
- Stop when you reach the center
2. The number of rings depends on n
:
- Each ring consumes 2 rows and 2 columns
- Therefore, there are
Math.floor(n / 2)
full rings - If
n
is odd, there's one cell left in the center
Step 2: Variables That Control the Process
In an effective solution, these variables play critical roles:
startRow
/startCol
: Where the current ring beginsloopCount = Math.floor(n / 2)
: How many rings to drawoffset
: Ensures we shrink the boundary each time (so the next inner ring doesn't overwrite the previous one)count
: The number to fill in
👉 Think of it like peeling an onion. Each loop strips away one layer of the matrix.
Step 3: One Ring at a Time
Inside each loop, we fill four walls:
1. Top row (left → right)
From (startRow, startCol)
to (startRow, n - offset)
.
2. Right column (top → bottom)
From (startRow, n - offset)
to (n - offset, n - offset)
.
3. Bottom row (right → left)
From (n - offset, n - offset)
to (n - offset, startCol)
.
4. Left column (bottom → top)
From (n - offset, startCol)
back up to (startRow, startCol)
.
After finishing one ring:
- Increment
startRow
andstartCol
- Increment
offset
Step 4: The Center Cell
When n
is odd, we still have one untouched cell in the center. That's why we handle it separately:
if (n % 2 === 1) {
res[mid][mid] = count;
}
Step 5: Complete Implementation
var generateMatrix = function (n) {
let startRow = 0,
startCol = 0;
let loopCount = Math.floor(n / 2);
let mid = Math.floor(n / 2);
let offset = 1;
let count = 1;
let res = new Array(n).fill(0).map(() => new Array(n).fill(0));
while (loopCount--) {
let row = startRow,
col = startCol;
// Top row (left to right)
for (; col < n - offset; col++) res[row][col] = count++;
// Right column (top to bottom)
for (; row < n - offset; row++) res[row][col] = count++;
// Bottom row (right to left)
for (; col > startCol; col--) res[row][col] = count++;
// Left column (bottom to top)
for (; row > startRow; row--) res[row][col] = count++;
// Move to the next ring
startRow++;
startCol++;
offset++;
}
// Handle center cell for odd n
if (n % 2 === 1) res[mid][mid] = count;
return res;
};
Step 6: Why This Strategy Works
The genius of this approach is regularity:
- Each ring follows the same 4 steps
- The boundaries naturally shrink after each ring
- Stopping conditions (
loopCount
and odd-center check) guarantee correctness
You don't need to think about corner cases for different n
. The pattern is consistent and scales elegantly.
Alternative: Boundary-Based Approach
Here's another clean implementation using explicit boundaries:
var generateMatrix = function(n) {
const matrix = Array.from({ length: n }, () => Array(n).fill(0));
let top = 0, bottom = n - 1;
let left = 0, right = n - 1;
let num = 1;
while (top <= bottom && left <= right) {
// Fill top row from left to right
for (let j = left; j <= right; j++) {
matrix[top][j] = num++;
}
top++;
// Fill right column from top to bottom
for (let i = top; i <= bottom; i++) {
matrix[i][right] = num++;
}
right--;
// Fill bottom row from right to left (if row exists)
if (top <= bottom) {
for (let j = right; j >= left; j--) {
matrix[bottom][j] = num++;
}
bottom--;
}
// Fill left column from bottom to top (if column exists)
if (left <= right) {
for (let i = bottom; i >= top; i--) {
matrix[i][left] = num++;
}
left++;
}
}
return matrix;
};
Time and Space Complexity
- Time Complexity: O(n²) - We need to fill every cell in the n×n matrix
- Space Complexity: O(n²) - For the result matrix (excluding input)
Key Takeaways
- Think of the spiral as concentric layers (rings)
- Each loop = one ring = four straight-line fills
- Keep track of boundaries (
startRow
,startCol
,offset
) or use explicit boundaries - For odd
n
, handle the center cell separately
Once you frame the problem as "peel layers one by one," the code almost writes itself.
Matrix Traversal Mindset
👉 Next time you see a "matrix traversal" problem, ask yourself:
- Can I decompose this into layers or boundaries?
- Can I repeat the same steps for each layer?
That mindset shift often transforms a confusing traversal problem into a clean and predictable solution.
Common Mistakes to Avoid
- Array initialization: Make sure to create independent rows (avoid shared references)
- Boundary conditions: Be careful with
<=
vs<
in your loops - Center cell: Don't forget to handle the center for odd-sized matrices
- Off-by-one errors: Test with small examples like n=1, n=2, n=3
This layer-by-layer strategy is not just elegant—it's also robust and easy to debug, making it perfect for coding interviews and real-world applications.