Adjacency Matrix
A V×V grid where cell [u][v] stores whether (or how heavily) u connects to v, giving O(1) edge lookup at O(V²) space.
Definition
An adjacency matrix represents a graph with V vertices as a V × V array M. For an unweighted graph M[u][v] = 1 if edge u → v exists and 0 otherwise; for a Weighted Graph it stores the weight, with ∞ (or null) for "no edge". An Undirected Graph has a symmetric matrix.
Its strength is O(1) edge existence and weight lookup, and natural algebraic operations: M²[u][v] counts length-2 walks, Floyd-Warshall runs directly on the matrix, and the transpose is the reversed graph. Its weakness is O(V²) memory regardless of E, and O(V) to enumerate neighbors, which makes Breadth-First Search (BFS) and Depth-First Search (DFS) O(V²).
Use it when the graph is dense (E close to V²), when V ≤ ~10⁴, or when the algorithm is matrix-shaped. For sparse graphs an Adjacency List is strictly better.
Intuition
A mental model before the formal terms.
A mileage chart in the back of a road atlas: cities down the side and across the top, and the number where a row meets a column is the distance. Looking up any pair is instant; listing everywhere reachable from one city means scanning an entire row, including all the blanks.
The chart has as many cells as there are city pairs. With 1,000 cities that is a million cells even if there are only 2,000 roads — 99.8% blank.
How it works
- Allocate
MasVrows ofVcells initialised to0(unweighted) or∞(weighted). SetM[u][u] = 0for weighted matrices if self-distance is used. addEdge(u, v, w):M[u][v] = w; for undirected alsoM[v][u] = w.hasEdge(u, v):M[u][v] != 0(or!= ∞).neighbors(u): iteratevfrom0toV-1and yield those withM[u][v]set —O(V)regardless of degree.- Floyd-Warshall:
for k: for i: for j: M[i][j] = min(M[i][j], M[i][k] + M[k][j])computes all-pairs shortest paths in place. - Bit-packing: for unweighted graphs each row can be a bitset, cutting memory by 64× and enabling fast transitive-closure via bitwise OR.
Why it works
The matrix is a direct encoding of the edge relation as a function V × V → {0, 1} (or weights), so lookup is array indexing.
Matrix multiplication counts walks: (M^k)[u][v] = Σ over intermediate vertices of products of edge indicators, which is exactly the number of length-k walks from u to v.
Floyd-Warshall is DP over the matrix: after iteration k, M[i][j] is the shortest path using only intermediates ≤ k.
Operations
| Operation | Description | Cost |
|---|---|---|
| addEdge(u, v, w) | Set one cell (two for undirected). | O(1) |
| removeEdge(u, v) | Reset the cell. | O(1) |
| hasEdge / weight(u, v) | Read the cell. | O(1) |
| neighbors(u) | Scan row u. | O(V) |
| degree(u) | Count non-empty cells in row u (or maintain a counter). | O(V) |
| addVertex | Reallocate a (V+1)×(V+1) matrix. | O(V²) |
| transpose | Swap M[i][j] and M[j][i]; gives the reversed graph. | O(V²) |
| allPairs (Floyd-Warshall) | In-place DP. | O(V³) |
Recognition
How to tell a problem wants this.
- The input is already given as a matrix (
isConnected[i][j],graph[i][j], a distance table). V ≤ 500or so, and you need all-pairs shortest paths or transitive closure.- Frequent "is there an edge between u and v" queries on a dense graph.
- Problems phrased in terms of walks of length k or matrix exponentiation.
Interactive demo
Play, step, change the input. ← → and space work too.
Showing the closely related Floyd-Warshall visualization.
| A | B | C | D | E | |
|---|---|---|---|---|---|
| A | 0 | 3 | ∞ | ∞ | 8 |
| B | ∞ | 0 | 1 | 7 | ∞ |
| C | 2 | ∞ | 0 | 1 | ∞ |
| D | ∞ | ∞ | ∞ | 0 | 2 |
| E | 4 | ∞ | ∞ | ∞ | 0 |
1dist[i][j] = w(i,j) if edge, 0 if i == j, else ∞2for k in nodes: # allowed intermediate3 for i in nodes:4 for j in nodes:5 if dist[i][k] + dist[k][j] < dist[i][j]:6 dist[i][j] = dist[i][k] + dist[k][j]7return distPseudocode
1M = [[INF]*V for _ in range(V)]; M[i][i] = 02add_edge(u, v, w): M[u][v] = w (and M[v][u] = w if undirected)3has_edge(u, v): return M[u][v] != INF4neighbors(u): return [v for v in range(V) if M[u][v] != INF and v != u]5floyd_warshall(): for k: for i: for j: M[i][j] = min(M[i][j], M[i][k] + M[k][j])Implementation
1class AdjacencyMatrix:2 """n x n boolean (or weight) grid: cell (u, v) says whether u -> v exists.3 Dense storage: O(V^2) memory regardless of how many edges there are."""4 51 · State: a flat V*V bytearray indexed as u * n + v6 def __init__(self, n: int, directed: bool = False) -> None:7 self.n = n8 self.directed = directed9 self.cells = bytearray(n * n)10 112 · Edge insertion and lookup are both O(1) — the defining advantage12 def add_edge(self, u: int, v: int) -> None:13 self.cells[u * self.n + v] = 114 if not self.directed:15 self.cells[v * self.n + u] = 116 17 def has_edge(self, u: int, v: int) -> bool:18 return self.cells[u * self.n + v] != 019 203 · Enumerating neighbours costs O(V), even for a vertex of degree 121 def neighbours(self, u: int) -> list[int]:22 base = u * self.n23 return [v for v in range(self.n) if self.cells[base + v]]24 254 · A full traversal is O(V^2), which is why sparse graphs use lists26 def edge_count(self) -> int:27 count = sum(self.cells)28 return count if self.directed else count // 2 # undirected cells are symmetric29 305 · Transpose reverses every edge by mirroring across the diagonal31 def transposed(self) -> "AdjacencyMatrix":32 t = AdjacencyMatrix(self.n, directed=True)33 for u in range(self.n):34 base = u * self.n35 for v in range(self.n):36 if self.cells[base + v]:37 t.cells[v * self.n + u] = 138 return tbytearray(n * n)is a mutable, contiguous, zero-filled byte buffer — the closest Python equivalent to aUint8Array.neighbourshoistsbase = u * self.nout of the comprehension so the multiplication happens once per call rather than once per column.sum(self.cells)counts set cells in C, which is far faster than a Python-level double loop over V^2 cells.count // 2halves the undirected total; integer division makes the intent explicit and avoids a float result.if self.cells[base + v]relies on 0 being falsy, which is idiomatic Python and avoids an explicit!= 0.
bytearray stores one byte per cell; a list[list[int]] would store pointers to boxed ints and use roughly 60x more memory.
bytearraygives one byte per cell with no boxing; a nestedlistofintis the naive version and is enormous by comparison.- NumPy is the real answer for anything matrix-shaped:
np.zeros((n, n), dtype=np.uint8)plus vectorised row operations, andm.Tfor the transpose. sum(bytearray)iterates in C; the equivalentsum(1 for c in cells if c)is a Python-level loop and much slower.scipy.sparsebridges the two worlds when the graph is sparse but matrix algebra is still wanted.
- Assigning a value above 255 into a
bytearray, which raisesValueErrorrather than truncating (unlikeUint8Arrayin JavaScript). - Building the matrix as
[[0] * n] * n, which aliases one row n times — the same trap as the adjacency list. - Reaching for a matrix on a sparse graph, where
scipy.sparseor adefaultdict(list)is orders of magnitude smaller.
- Byte-dense storage: C++
std::vector<std::uint8_t>, JS/TSUint8Array, Pythonbytearray— and in every case the naive nested container is roughly an order of magnitude larger. std::vector<bool>is a unique C++ trap: it bit-packs and returns a proxy fromoperator[], so it is deliberately avoided here in favour ofuint8_t.- Out-of-range writes:
Uint8Arraytruncates silently (256becomes0), PythonbytearrayraisesValueError, and C++operator[]is undefined behaviour — three different reactions to the same mistake. - Index overflow in
u * n + vis a real hazard only in C++ with 32-bitint; JavaScript doubles stay exact to 2^53 and Python integers are unbounded.
Complexity
| Operation | Average | Worst | Note |
|---|---|---|---|
| Access | O(1) | O(1) | Edge (u, v) by index. |
| Search | O(1) | O(1) | Edge existence. |
| Insert | O(1) | O(1) | Edge; adding a vertex is O(V²). |
| Delete | O(1) | O(1) | Edge. |
| Update | O(1) | O(1) | Change a weight. |
| Neighbors | O(V) | O(V) | |
| BFS / DFS | O(V²) | O(V²) | |
| Floyd-Warshall | O(V³) | O(V³) | |
| Space | O(V²) | Independent of E; bitset rows reduce the constant by 64×. | |
Advantages & disadvantages
O(1)edge lookup, insertion, and deletion.- Simple, cache-friendly for dense graphs; no pointers or dynamic allocation.
- Algebraic operations (powers, transpose, Floyd-Warshall) map directly onto the array.
- Bitset rows make transitive closure and set operations very fast.
O(V²)memory even for a graph withVedges — infeasible pastV ≈ 10⁴–10⁵.- Enumerating neighbors is
O(V), so BFS/DFS becomeO(V²)instead ofO(V + E). - Adding a vertex requires reallocating the whole matrix.
- Cannot represent parallel edges without extra structure (only one cell per pair).
Use cases
- Floyd-Warshall all-pairs shortest paths.
- Number of Provinces / friend circles given as
isConnected[i][j]. - Transitive closure and reachability matrices.
- Counting walks and paths of a fixed length via matrix exponentiation.
- Small dense graphs in DP-over-subsets problems (Bitmask DP, TSP).
- Dense graphs where
E ≈ V². - Frequent
O(1)edge-existence or weight queries. - All-pairs algorithms (Floyd-Warshall), transitive closure, walk counting.
- Small
V(≤ a few thousand) where simplicity matters more than memory.
- Sparse graphs with large
V— memory isO(V²)and traversals areO(V²); use an Adjacency List. - Vertices are added dynamically — resizing the matrix is
O(V²). - Multigraphs with parallel edges — a cell holds only one value.
Alternatives
Common mistakes
- Using
0as "no edge" in a weighted matrix where zero-weight edges are legal. - Using
Integer.MAX_VALUEas infinity and overflowing ond[i][k] + d[k][j]— useINF = MAX / 4or check before adding. - Forgetting to set both
M[u][v]andM[v][u]for undirected graphs. - Running BFS via row scans on a sparse graph with
V = 10⁵—10¹⁰operations. - Iterating
kas the innermost loop in Floyd-Warshall — it must be the outermost.
Interview patterns
- Number of Provinces: DFS/union-find over an
isConnectedmatrix. - Find the Celebrity:
knows(a, b)is a matrix oracle; two-pointer elimination. - Floyd-Warshall for "city with the smallest number of neighbors within threshold".
- Count walks of length k with fast matrix exponentiation.
- TSP / Hamiltonian path with bitmask DP over a distance matrix.
- Choosing between BFS, DFS, Dijkstra and DPAdvanced
- Stack versus queueBeginner
- Recursion versus iterationIntermediate
- When space complexity mattersIntermediate
- Course ScheduleIntermediate
- Network Delay TimeAdvanced
- Number of IslandsIntermediate