Mustaque Nadim Academy
Arrays & Strings

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 arraysrows 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_size

Constant-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  6

The 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)          # 21
const 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);          // 21
int[][] 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

OperationTimeWhy
Access matrix[r][c]O(1)two address computations
Full traversalO(rows × cols)must touch every cell
SpaceO(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] is O(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

On this page