Problem statement (short): Given nodes and edges, choose a data structure to store the graph so algorithms (BFS, DFS, Dijkstra, MST, etc.) can run efficiently. Different representations change runtime, memory usage, and simplicity of implementation.
Why it matters:
- Some algorithms iterate neighbors fast → adjacency list is great.
- Some require O(1) edge-existence checks → adjacency matrix helps.
- Weighted graphs need to store weights per edge.
Choosing the right representation is one of the easiest ways to improve performance.
- Adjacency Matrix - 2D array
mat[u][v]indicates edge (or weight). - Adjacency List -
adj[u] = [v1, v2, ...]. - Edge List -
[{u, v}]or[{u, v, w}]- useful for Kruskal and offline processing.
- Use adj list for sparse graphs (E ≈ V or E ≪ V²).
- Use adj matrix for dense graphs (E ≈ V²) or when you need O(1) edge queries.
- Use edge list when sorting edges or running offline algorithms.
An n × n 2D array where:
mat[u][v] = true/falsefor unweighted graphsmat[u][v] = weightorInfinityfor weighted graphs
- Dense graphs
- Fast O(1)
hasEdge(u, v)checks - Simple educational examples
- Memory: O(n²)
- Iterate neighbors of
u: O(n) - Edge existence: O(1)
const n = 5;
const mat = Array.from({ length: n }, () => Array(n).fill(false));
mat[u][v] = true; // directed
mat[v][u] = true; // undirected
const exists = mat[a][b];const INF = Infinity;
const mat = Array.from({ length: n }, () => Array(n).fill(INF));
for (let i = 0; i < n; i++) {
mat[i][i] = 0;
}
mat[u][v] = weight;- Huge memory usage for large
n - Iterating neighbors always costs O(n) even if graph is sparse
An array of arrays where each index stores its neighbors.
For weighted graphs: store objects like { to, weight }.
- Most real-world graphs
- Sparse graphs
- Fast neighbor iteration
- Memory: O(n + m)
- Iterate neighbors: O(deg(u))
- Add edge: O(1)
const adj = Array.from({ length: n }, () => []);
adj[u].push(v); // directed
adj[v].push(u); // undirectedconst adj = Array.from({ length: n }, () => []);
adj[u].push({ to: v, weight: w });
adj[v].push({ to: u, weight: w }); // if undirectedhasEdge(u,v)costs O(deg(u))- No built-in capacity control like Java/C++
Structure:
const edges = [];
edges.push({ u, v }); // unweighted
edges.push({ u, v, w }); // weightedUse-cases:
- Kruskal’s MST
- Bellman-Ford
- Sorting edges
Memory: O(m) Neighbor queries: O(m)
- Adjacency List → best for Dijkstra
- Adjacency Matrix → store weight or
Infinity - Edge List → store
{u,v,w}
⚠ Important: JavaScript uses Number (floating-point). For very large sums, consider careful overflow handling. For competitive programming scale, Number is usually fine.
| Operation | Adjacency List | Adjacency Matrix | Edge List |
|---|---|---|---|
| Memory | O(n + m) | O(n²) | O(m) |
| Iterate neighbors | O(deg(u)) | O(n) | O(m) |
| hasEdge(u,v) | O(deg(u)) | O(1) | O(m) |
| Add edge | O(1) | O(1) | O(1) |
| Remove edge | O(deg(u)) | O(1) | O(m) |
function matToList(mat) {
const n = mat.length;
const adj = Array.from({ length: n }, () => []);
for (let u = 0; u < n; u++) {
for (let v = 0; v < n; v++) {
if (mat[u][v]) adj[u].push(v);
}
}
return adj;
}function listToMat(adj) {
const n = adj.length;
const mat = Array.from({ length: n }, () => Array(n).fill(false));
for (let u = 0; u < n; u++) {
for (const v of adj[u]) {
mat[u][v] = true;
}
}
return mat;
}- n = 4
- edges: (0,1), (1,2), (2,3)
Check:
adj[1] // should contain 0 and 2
mat[0][1] === true
mat[3][0] === false- edges: (0,1,5), (1,2,2)
- Dijkstra from 0 → dist[2] = 7
// graph_representations.js
const INF = Infinity;
// - Adjacency Matrix (Unweighted) -
function makeAdjMatrixUnweighted(n) {
return Array.from({ length: n }, () => Array(n).fill(false));
}
function addEdgeMatUnweighted(mat, u, v, undirected = true) {
mat[u][v] = true;
if (undirected) mat[v][u] = true;
}
// - Adjacency Matrix (Weighted) -
function makeAdjMatrixWeighted(n) {
const mat = Array.from({ length: n }, () => Array(n).fill(INF));
for (let i = 0; i < n; i++) mat[i][i] = 0;
return mat;
}
function addEdgeMatWeighted(mat, u, v, w, undirected = true) {
mat[u][v] = w;
if (undirected) mat[v][u] = w;
}
// - Adjacency List (Unweighted) -
function makeAdjListUnweighted(n) {
return Array.from({ length: n }, () => []);
}
function addEdgeListUnweighted(adj, u, v, undirected = true) {
adj[u].push(v);
if (undirected) adj[v].push(u);
}
// - Adjacency List (Weighted) -
function makeAdjListWeighted(n) {
return Array.from({ length: n }, () => []);
}
function addEdgeListWeighted(adj, u, v, w, undirected = true) {
adj[u].push({ to: v, weight: w });
if (undirected) adj[v].push({ to: u, weight: w });
}
// - Edge List -
function makeEdgeList() {
return [];
}
function addEdgeEdgeList(edges, u, v, w) {
edges.push({ u, v, w });
}
// - Dijkstra -
function dijkstra(src, adj) {
const n = adj.length;
const dist = Array(n).fill(INF);
dist[src] = 0;
const pq = [];
pq.push({ node: src, dist: 0 });
while (pq.length) {
pq.sort((a, b) => a.dist - b.dist);
const { node: u, dist: d } = pq.shift();
if (d !== dist[u]) continue;
for (const { to: v, weight: w } of adj[u]) {
if (dist[v] > d + w) {
dist[v] = d + w;
pq.push({ node: v, dist: dist[v] });
}
}
}
return dist;
}
// - Demo -
function demo() {
console.log("=== Graph Demo ===");
const adj = makeAdjListWeighted(3);
addEdgeListWeighted(adj, 0, 1, 5);
addEdgeListWeighted(adj, 1, 2, 2);
const dist = dijkstra(0, adj);
console.log("Dijkstra dist 0->2:", dist[2], "(expect 7)");
}
demo();- Adjacency matrix → O(n²) memory, O(1) edge checks
- Adjacency list → O(n + m) memory, fast neighbor iteration
- Edge list → O(m) memory, good for sorting edges
If m ≪ n², adjacency list almost always wins.
- For small dense graphs → matrix works well
- For large graphs → always use adjacency list
- Use
Infinityfor “no edge” in weighted matrix - Avoid frequent
Array.sort()in Dijkstra (use a proper min-heap for production) - For fast
hasEdge(u,v)in sparse graphs → useSet:
const adj = Array.from({ length: n }, () => new Set());
adj[u].add(v);- Use
Mapfor weighted adjacency:
const adj = Array.from({ length: n }, () => new Map());
adj[u].set(v, weight);- CSR-like structure for performance-heavy workloads
- Bitsets using typed arrays
- Dynamic graph structures
Q: Which to memorize for interviews? A: Adjacency list.
Q: When use matrix? A: Small dense graphs or heavy edge-existence queries.
Q: Weighted Dijkstra best structure? A: Adjacency list.
Q: Large n (10⁵)? A: Matrix impossible. Use adjacency list.
- Graph dense or sparse?
- Need fast
hasEdge? - Weighted or unweighted?
- Could numbers overflow?
- Need fast priority queue?