2D arrays & matrix traversal
In one line
A matrix is a grid of rows and columns; most grid questions come down to visiting cells in the right order and stepping to neighbours with a direction list and a bounds check.
What it is
A 2D array (matrix or grid) has R rows and C columns, and cell (r, c) sits in row r, column c. In memory it is usually stored row by row (row-major order, used by C and NumPy by default), so cell (r, c) is at flat index r * C + c.
Grid problems fall into a few families: fixed-order walks (row by row, column by column, diagonals, spiral), geometric transforms (transpose, rotate), and treating the grid as a graph where each cell links to its 4 (or 8) neighbours, so BFS and DFS apply.
Why interviewers ask
Grids show up constantly: images, game boards, maps, spreadsheets, island counting and shortest paths in a maze. Interviewers watch for clean index handling (no off-by-one, no out-of-bounds reads), correct visited tracking, and whether you can state complexity in terms of R and C rather than a vague n.
How it works
- 1Dimensions: rows = len(grid), cols = len(grid[0]). Check for an empty grid first.
- 2Neighbours: keep a direction list such as [(1, 0), (-1, 0), (0, 1), (0, -1)] and add each to (r, c). Always check 0 <= nr < rows and 0 <= nc < cols before reading.
- 3Visited: mark cells when they are added to the queue or stack (not when popped), either in a set or by overwriting the cell if you are allowed to modify the input.
- 4Diagonals: every cell on the same top-left to bottom-right diagonal has the same r - c; on the same anti-diagonal, the same r + c. Group by those values.
- 5Rotate an n x n matrix 90 degrees clockwise in place: transpose (swap grid[i][j] with grid[j][i] for j > i), then reverse each row.
- 6Spiral order: keep four boundaries (top, bottom, left, right), walk one edge, shrink that boundary, and stop when they cross.
Worked example · Counting islands in a 3 x 3 grid
- Grid rows: ['1','1','0'], ['0','1','0'], ['0','0','1']. '1' is land, '0' is water; land connects up, down, left and right only.
- Scan row by row. (0,0) is '1': islands = 1. Mark it '0' and start BFS with queue [(0,0)].
- Pop (0,0). Neighbours: (1,0) is water, (0,1) is land, mark it and enqueue. Queue [(0,1)].
- Pop (0,1). Neighbours: (1,1) is land, mark and enqueue; (0,0) already marked; (0,2) water. Queue [(1,1)].
- Pop (1,1). Its neighbours (2,1), (0,1), (1,2), (1,0) are all water or marked. Queue empty: island 1 is fully erased.
- Continue scanning: (0,2), (1,0), (1,2), (2,0), (2,1) are '0'. (2,2) is '1': islands = 2, BFS finds no land neighbours.
- Answer 2. (2,2) touches (1,1) only diagonally, which does not count. Every cell is visited a constant number of times: O(R * C).
Picture it
A grid is a city laid out in blocks. Walking a matrix is planning a route: row-major is reading street by street, spiral is circling the city and moving inward, and BFS from a cell is a rumour spreading to adjacent houses one block at a time. The bounds check is the city limit sign.
Reference code
from collections import deque
def num_islands(grid):
if not grid:
return 0
rows, cols = len(grid), len(grid[0])
dirs = [(1, 0), (-1, 0), (0, 1), (0, -1)]
islands = 0
for r in range(rows):
for c in range(cols):
if grid[r][c] != '1':
continue
islands += 1 # new island found
grid[r][c] = '0' # mark visited
q = deque([(r, c)])
while q:
cr, cc = q.popleft()
for dr, dc in dirs:
nr, nc = cr + dr, cc + dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == '1':
grid[nr][nc] = '0'
q.append((nr, nc))
return islands
Each unvisited land cell starts a new island, and a BFS erases every land cell connected to it so it is never counted again. Cells are marked when enqueued, so no cell enters the queue twice. Time O(R * C); extra space for the queue is O(R * C) in the worst case. The grid is modified in place; copy it first if the caller needs it intact.
Key patterns
Grid as a graph (BFS/DFS)
When: Islands, flood fill, connected regions, shortest path in a maze
How: Cells are nodes, 4 or 8 neighbours are edges; mark visited when enqueuing. BFS gives shortest paths in unweighted grids.
Multi-source BFS
When: Distance from the nearest of many sources (rotting oranges, nearest exit)
How: Put all sources in the queue at distance 0, then run one BFS.
Layer walks (spiral)
When: Print or fill a matrix in spiral order
How: Track top, bottom, left, right; walk one edge, then move that boundary inward.
Transpose + reverse (rotate)
When: Rotate an n x n image 90 degrees in place
How: Clockwise: transpose then reverse each row. Counter-clockwise: transpose then reverse each column.
Diagonal keys
When: Group or check cells on the same diagonal (N-Queens, diagonal traverse)
How: Same diagonal: r - c is equal. Same anti-diagonal: r + c is equal.
Real-world use
Paint bucket in image editors
The fill tool in Photoshop, GIMP and MS Paint is flood fill: starting from the clicked pixel, it visits neighbouring pixels of a similar colour with BFS or DFS, exactly like island counting.
Tile maps in games
Games like Minesweeper and many 2D strategy games store the board as a grid; Minesweeper's reveal of an empty area is a flood fill, and unit pathfinding runs BFS or A* over grid cells.
NumPy, images and cache order
NumPy arrays default to row-major (C order). Looping row by row reads memory sequentially and is much faster than looping column by column over a large image, because each cache line holds neighbouring cells in the same row.
Spreadsheets
Excel and Google Sheets treat a sheet as a grid addressed by (row, column); fill-down, range sums and conditional formatting are range traversals over that grid.
In a project
A warehouse robot planner stores the floor as a 200 x 300 grid (shelves blocked, aisles free) and must find the shortest route from the dock to any shelf, plus count disconnected free areas after shelves are moved.
- Represent the floor as a grid of 0 (free) and 1 (blocked) and use a 4-direction list for moves.
- Run BFS from the dock once, storing the step distance to every free cell; then every shelf query is an O(1) lookup instead of a new search.
- After each layout change, count connected free regions with BFS (island counting) and alert if a region is cut off from the dock.
- Use row-major loops and a flat visited array of size R * C indexed by r * C + c to keep the 60,000-cell search fast.
Impact: Route planning for every shelf comes from one O(R * C) pass instead of one search per request, and layout mistakes that trap robots are caught before deployment rather than on the floor.
Trade-offs
Use it when
- The data is naturally a grid: images, boards, maps, tables
- Neighbour relations are fixed by position (up, down, left, right)
- You need O(1) access to any cell by coordinates
Avoid it when
- The grid is huge but almost empty: store only occupied cells in a hash map or sparse format
- Connections are not positional: use an adjacency list graph
- Deep recursive DFS on large grids in Python: a 1000 x 1000 region can exceed the recursion limit, so use BFS or an explicit stack
Cost: Traversals and BFS/DFS over a grid: O(R * C) time. Visited set or queue: O(R * C) space worst case. In-place rotate and spiral: O(1) extra space besides output.
Common mistakes
- Swapping rows and columns, for example using len(grid) for both dimensions on a non-square grid.
- Reading neighbours before the bounds check, causing index errors, or in Python silently wrapping around with negative indexes.
- Marking cells visited only when popped, which lets the same cell be enqueued many times.
- Creating a grid with [[0] * C] * R in Python: every row is the same list, so one write changes all rows. Use [[0] * C for _ in range(R)].
- Using recursive DFS on a large grid and hitting Python's recursion limit.
Interview questions
Quick revision
- rows = len(grid), cols = len(grid[0]); handle empty grids.
- Row-major flat index: r * cols + c.
- Directions list + bounds check: 0 <= nr < rows and 0 <= nc < cols.
- Mark visited when enqueuing, not when popping.
- Same diagonal: r - c; anti-diagonal: r + c.
- Rotate clockwise = transpose + reverse each row.
- BFS = shortest path in an unweighted grid; multi-source BFS for nearest-of-many.
- Python: [[0] * C for _ in range(R)], never [[0] * C] * R.
Check yourself
1. In a row-major 4 x 5 matrix (4 rows, 5 columns), what is the flat index of cell (2, 3)?
2. Which sequence rotates an n x n matrix 90 degrees clockwise in place?
3. Cells (1, 3) and (3, 5) lie on the same top-left to bottom-right diagonal because...
4. Why should a BFS on a grid mark a cell visited when it is enqueued?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.