2D-array problems look intimidating but reduce to a few reusable moves.
Move 1: rotate 90° = transpose + reverse rows
// Clockwise rotation, in place
function rotate(m) {
const n = m.length;
for (let r = 0; r < n; r++) // transpose (flip across diagonal)
for (let c = r + 1; c < n; c++)
[m[r][c], m[c][r]] = [m[c][r], m[r][c]];
for (const row of m) row.reverse(); // then mirror each row
return m;
}Nobody derives rotation index math under pressure — everyone who passes just knows this two-step decomposition. (Counter-clockwise: transpose, then reverse columns.)
Move 2: spiral order = four shrinking walls
let top = 0, bottom = rows - 1, left = 0, right = cols - 1;
while (top <= bottom && left <= right) {
for (let c = left; c <= right; c++) out.push(m[top][c]); top++;
for (let r = top; r <= bottom; r++) out.push(m[r][right]); right--;
if (top <= bottom) { for (let c = right; c >= left; c--) out.push(m[bottom][c]); bottom--; }
if (left <= right) { for (let r = bottom; r >= top; r--) out.push(m[r][left]); left++; }
}Four boundaries that shrink inward. The two if guards prevent double-visiting on non-square matrices — the exact edge case interviewers test.
Move 3: use the matrix itself as storage
"Set entire row/column to zero if a cell is zero, in O(1) space" — use the first row and column as marker arrays instead of allocating new ones. This borrow-the-input trick recurs across in-place matrix problems (game of life uses spare bits similarly).
Spot it when…
- Rotation/spiral/diagonal traversal — recall the template, don't derive.
- "In place" + matrix → the first-row/column marker trick.
- Islands/regions in a grid → that's DFS flood fill, not this.
Indexing sanity: m[row][col], rows first. Half of matrix bugs are r/c swaps — name your loop variables r and c, never i and j.