Graph representation
A graph is made of two things: vertices (nodes) and the edges that connect them. Everything else is built on top of those two.
A graph has three basic properties, and you should pin all three down the moment you read the problem:
- Directed vs undirected — does an edge go one way? A Twitter "follow" is directed (you follow them, they don't follow you); a Facebook "friendship" is undirected
- Weighted vs unweighted — does an edge carry a cost? On a road map the distance/time is a weight; in a plain acquaintance graph there is none
- Cyclic or acyclic — a directed graph with no cycles is a DAG, and that is the core model for task dependencies, build systems and migration ordering
Size notation: V for the number of vertices, E for the number of edges. Density matters too: with E ≈ V the graph is sparse, with E ≈ V² it is dense. The overwhelming majority of real-world graphs are sparse.
There are two standard ways to hold a graph in memory.
Adjacency list — for each vertex, the list of its neighbours. In practice a Map<vertex, neighbours> or an array of arrays. Memory is O(V + E). This is the default for sparse graphs — it is what you will write in about 90% of real problems.
Adjacency matrix — a V × V table where matrix[u][v] says whether an edge exists between two vertices (or holds its weight). Memory is O(V²), and the edge count plays no part: a 10,000-vertex graph with just 3 edges still allocates 100 million cells.
The rule in one sentence: if you keep asking "is there an edge here?" and the graph is small/dense → matrix; if you iterate neighbours and the graph is large/sparse → list.
| Operation | Adjacency list | Adjacency matrix |
|---|---|---|
| Memory | O(V + E) | O(V²) — independent of edge count |
| Add an edge | O(1) | O(1) |
| Check "is there a u–v edge?" | O(deg(u)) — you scan the neighbours | O(1) — direct index |
| Iterate all neighbours of u | O(deg(u)) — optimal | O(V) — you must read the whole row |
| Remove an edge | O(deg(u)) | O(1) |
| When it wins | Sparse graphs, BFS/DFS, real networks | Small dense graphs, frequent edge checks, matrix algorithms (Floyd-Warshall) |
The hard part is not knowing a graph algorithm — it is seeing that the problem is a graph. All of these are graphs:
- Social network — vertex = user, edge = friendship/follow; "how many hops between two people" is BFS
- Routing — vertex = intersection, edge = road segment, weight = time; the shortest path is Dijkstra
- Dependency graph — vertex = module/task, edge = "this must finish first"; build order is a topological sort, and a circular dependency is cycle detection
- Image / game board / maze — every cell is a vertex, adjacent cells are edges; you never write a
Graphclass, you compute neighbours with[[0,1],[1,0],[0,-1],[-1,0]] - State graph — vertex = a state of the system, edge = a transition; "the minimum number of steps from state A to state B" is BFS again
Those last two are what candidates miss most often in interviews: they say "this is a matrix problem" and start writing manual loops, when it is a disguised graph problem.
Interview tip: before writing any code on a graph problem, ask four questions out loud — is it directed? are there weights? can there be cycles? roughly how large are V and E? Then justify your choice: "V is up to 10⁵ and E is close to V, so it's sparse — I'll take an adjacency list; a matrix would need 10¹⁰ cells." That single sentence moves you from "memorised the algorithm" to "thinks about resources".