- Home
- /
- Tutorials
- /
- DSA Tutorial
- /
- Matrix Traversal and Manipulation
Matrix and 2D Arrays
Matrix Traversal and Manipulation
Grid problems are graph problems with implicit edges. Before any of that, though, you have to create the grid correctly - and the obvious way is wrong.
The bug to get out of the way first
Every row is the same array
javascript
// WRONG — fill() copies the reference, not the array.
const grid = new Array(3).fill(new Array(3).fill(0))
grid[0][0] = 1
console.log(grid)
// [[1,0,0], [1,0,0], [1,0,0]] — all three rows changed
// RIGHT — a fresh array per row.
const grid2 = Array.from({ length: 3 }, () => new Array(3).fill(0))
grid2[0][0] = 1
console.log(grid2)
// [[1,0,0], [0,0,0], [0,0,0]]This catches people constantly, and the symptom - writes appearing in rows you never touched - looks like a logic bug rather than a construction bug.
Transpose and rotate
Rotate 90 degrees in place
javascript
function rotate(matrix) {
const n = matrix.length
// Transpose: swap across the diagonal.
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
;[matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]]
}
}
// Then reverse each row.
for (const row of matrix) row.reverse()
return matrix
}
console.log(rotate([[1,2,3], [4,5,6], [7,8,9]]))
// [[7,4,1], [8,5,2], [9,6,3]]Transpose then reverse rows gives clockwise; transpose then reverse columns gives anticlockwise. j = i + 1 matters - starting at 0 swaps everything twice and undoes the work.
Spiral traversal
Spiral order
javascript
function spiralOrder(matrix) {
if (matrix.length === 0) return []
const out = []
let top = 0
let bottom = matrix.length - 1
let left = 0
let right = matrix[0].length - 1
while (top <= bottom && left <= right) {
for (let c = left; c <= right; c++) out.push(matrix[top][c])
top++
for (let r = top; r <= bottom; r++) out.push(matrix[r][right])
right--
// Guard: the row may already have been consumed above.
if (top <= bottom) {
for (let c = right; c >= left; c--) out.push(matrix[bottom][c])
bottom--
}
if (left <= right) {
for (let r = bottom; r >= top; r--) out.push(matrix[r][left])
left++
}
}
return out
}
console.log(spiralOrder([[1,2,3], [4,5,6], [7,8,9]]))
// [1,2,3,6,9,8,7,4,5]The two guards are not optional. On a single-row or single-column matrix, without them the last two loops re-read cells already collected.
Grids as graphs
Counting islands
javascript
function countIslands(grid) {
if (grid.length === 0) return 0
const rows = grid.length
const cols = grid[0].length
let count = 0
function sink(r, c) {
if (r < 0 || r >= rows || c < 0 || c >= cols) return
if (grid[r][c] !== "1") return
grid[r][c] = "0" // mark visited by overwriting
sink(r + 1, c)
sink(r - 1, c)
sink(r, c + 1)
sink(r, c - 1)
}
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (grid[r][c] === "1") {
count++
sink(r, c)
}
}
}
return count
}
console.log(countIslands([["1", "1", "0"], ["0", "1", "0"], ["0", "0", "1"]])) // 2This is DFS on an implicit graph. Overwriting the cell is the visited set. If mutating the input is not allowed, keep a separate visited grid - and on a very large grid use the iterative version, since recursion here can go rows × cols deep.
Neighbours without four copies of the same code
A direction array turns the four-way or eight-way neighbour check into one loop, and makes the bounds check impossible to get inconsistently wrong.
Directions, bounds, and flood fill
javascript
const DIRECTIONS = [[-1, 0], [1, 0], [0, -1], [0, 1]]
function neighbours(grid, row, col) {
const out = []
for (const [dr, dc] of DIRECTIONS) {
const r = row + dr
const c = col + dc
if (r >= 0 && r < grid.length && c >= 0 && c < grid[0].length) {
out.push([r, c])
}
}
return out
}
// Count islands by sinking each one as it is found.
function countIslands(grid) {
let count = 0
function sink(row, col) {
if (row < 0 || row >= grid.length) return
if (col < 0 || col >= grid[0].length) return
if (grid[row][col] !== "1") return
grid[row][col] = "0" // mark visited in place
for (const [r, c] of neighbours(grid, row, col)) sink(r, c)
}
for (let row = 0; row < grid.length; row++) {
for (let col = 0; col < grid[0].length; col++) {
if (grid[row][col] === "1") {
count++
sink(row, col)
}
}
}
return count
}
console.log(countIslands([["1", "0"], ["0", "1"]])) // 2Rotating in place
Rotating a square matrix 90° clockwise is a transpose followed by reversing each row. Both steps are in place, so no second grid is needed - and stating it as two named steps is far easier to get right than deriving the index arithmetic directly.
One trap when building grids: new Array(n).fill([]) puts the same array in every row, so writing to one row writes to all of them. Use Array.from({ length: n }, () => []) instead.
