Arrays & Hashing
Intermediate 10 min read

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

  1. 1Dimensions: rows = len(grid), cols = len(grid[0]). Check for an empty grid first.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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

  1. 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.
  2. Scan row by row. (0,0) is '1': islands = 1. Mark it '0' and start BFS with queue [(0,0)].
  3. Pop (0,0). Neighbours: (1,0) is water, (0,1) is land, mark it and enqueue. Queue [(0,1)].
  4. Pop (0,1). Neighbours: (1,1) is land, mark and enqueue; (0,0) already marked; (0,2) water. Queue [(1,1)].
  5. 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.
  6. 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.
  7. 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.

  1. Represent the floor as a grid of 0 (free) and 1 (blocked) and use a 4-direction list for moves.
  2. 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.
  3. After each layout change, count connected free regions with BFS (island counting) and alert if a region is cut off from the dock.
  4. 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.

Start free