The Matrix (2D Arrays)
Grids are everywhere — images, game boards, spreadsheets. A matrix is just an array of arrays, with its own traversal tricks.
The problem
You're writing a photo filter. A screen is a grid of pixels — 1920 columns by 1080 rows — and you need to reach the pixel at row 500, column 900 to brighten it. A chess engine has the same shape of problem: it asks "what's on square (row 3, column 5)?" thousands of times per move.
Both are grids, and both need the same thing: address any cell by its (row, column)
coordinates instantly, and sweep the whole grid in a predictable order. How do you lay a
two-dimensional world into a machine that only really knows how to line things up in one?
A first attempt
You might reach for a dictionary keyed by coordinate pairs: grid[(500, 900)] = value. It
works, and lookups are roughly constant time — but every cell now carries the overhead of a
hashed key, and there's no natural order to walk the neighbors of a pixel. Blurring a photo
means touching each pixel and its neighbors; with a coordinate dictionary you're hashing
your way around the neighborhood instead of just stepping one index over.
The dictionary throws away the grid's best feature: the cells have a regular, predictable layout. We can exploit that directly.
The insight
Store the grid as an array of arrays — rows arrays, each holding cols elements. The
outer index picks a row; the inner index picks a column. Underneath, most languages lay
these out in row-major order: row 0's cells, then row 1's, all contiguous. So reaching
matrix[r][c] is still just address arithmetic:
address(r, c) = base + (r × cols + c) × element_sizeConstant-time access to any cell, and neighbors sit right next to each other in memory — which makes row-by-row sweeps fast and cache-friendly.
How it works
Pick the shape: rows × cols
A matrix is defined by its dimensions. matrix[r] is the whole row r (itself an array);
matrix[r][c] is one cell.
Address a cell with two indices
matrix[r][c] reads row r, then column c within it. Two index operations, both O(1).
Sweep in row-major order
To visit every cell, loop rows on the outside and columns on the inside. This matches the memory layout, so it's the fast way to traverse.
col0 col1 col2
row0 [ 1 2 3 ]
row1 [ 4 5 6 ]
matrix[1][2] = 6
row-major memory: 1 2 3 4 5 6The code
matrix = [
[1, 2, 3],
[4, 5, 6],
]
# Access a single cell — O(1)
print(matrix[1][2]) # 6
# Row-major traversal — O(rows * cols)
total = 0
for row in matrix:
for value in row:
total += value
print(total) # 21const matrix: number[][] = [
[1, 2, 3],
[4, 5, 6],
];
console.log(matrix[1][2]); // 6
let total = 0;
for (let r = 0; r < matrix.length; r++) {
for (let c = 0; c < matrix[r].length; c++) {
total += matrix[r][c];
}
}
console.log(total); // 21int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
};
System.out.println(matrix[1][2]); // 6
int total = 0;
for (int r = 0; r < matrix.length; r++) {
for (int c = 0; c < matrix[r].length; c++) {
total += matrix[r][c];
}
}
System.out.println(total); // 21#include <stdio.h>
int main(void) {
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6},
};
printf("%d\n", matrix[1][2]); // 6
int total = 0;
for (int r = 0; r < 2; r++)
for (int c = 0; c < 3; c++)
total += matrix[r][c]; // row-major: fast
printf("%d\n", total); // 21
return 0;
}#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<vector<int>> matrix = {
{1, 2, 3},
{4, 5, 6},
};
cout << matrix[1][2] << "\n"; // 6
int total = 0;
for (size_t r = 0; r < matrix.size(); r++)
for (size_t c = 0; c < matrix[r].size(); c++)
total += matrix[r][c];
cout << total << "\n"; // 21
return 0;
}Complexity
| Operation | Time | Why |
|---|---|---|
Access matrix[r][c] | O(1) | two address computations |
| Full traversal | O(rows × cols) | must touch every cell |
| Space | O(rows × cols) | one slot per cell |
For an n × n grid, a full sweep is O(n²) — quadratic in the side length, but linear in
the number of cells. Don't let the two nested loops fool you into thinking it's wasteful;
you simply can't visit every cell in fewer steps than there are cells.
When to use it
Loop rows outside, columns inside
Because memory is row-major, iterating with the row loop on the outside and the column loop on the inside walks memory in order and stays cache-friendly. Flipping the loops (column-major traversal) can be several times slower on large grids for the exact same work.
Practice
Recap
- A matrix is an array of arrays;
matrix[r][c]isO(1)because it's two address computations, one per dimension. - Most languages store matrices in row-major order, so a full sweep costs
O(rows × cols)and runs fastest with rows on the outer loop. - Watch the classic build trap: replicate rows independently so they don't secretly share storage.
How is this guide?
Last updated on