Edge List
The graph as a flat list of (u, v[, w]) tuples — minimal, sortable, and exactly what Kruskal and Bellman-Ford need.
Definition
An edge list is the simplest graph representation: an array of edges, each a pair (u, v) or a triple (u, v, w). It is also the most common input format in problems ("edges[i] = [uᵢ, vᵢ, wᵢ]") and is usually converted into an Adjacency List before traversal.
A few algorithms work directly on the edge list and are simpler for it: Kruskal's Algorithm sorts edges by weight and unions endpoints with Union-Find (Disjoint Set Union); Bellman-Ford relaxes every edge V - 1 times; counting degrees or detecting a redundant connection is a single pass.
It offers no way to find a vertex's neighbors without scanning all E edges, so it is unsuitable for Breadth-First Search (BFS), Depth-First Search (DFS), or Dijkstra's Algorithm as-is.
Intuition
A mental model before the formal terms.
A spreadsheet with one row per road: from, to, length. Sorting the sheet by length and walking down it is Kruskal. Asking "which roads leave town X?" means reading every row — that is why you build a per-town index (an adjacency list) before doing traversals.
How it works
- Store
edges = [(u, v, w), …]; for an undirected graph store each edge once. addEdge: append.removeEdge: find and splice,O(E).toAdjacencyList(): createVempty lists and push each edge intoadj[u](andadj[v]).- Kruskal's Algorithm: sort by
w, then for each edgeunion(u, v); edges whose endpoints are already connected are skipped (they would form a cycle). - Bellman-Ford:
dist[s] = 0; repeatV - 1times: for every(u, v, w),dist[v] = min(dist[v], dist[u] + w). - Degree counting: one pass incrementing
deg[u]anddeg[v].
Why it works
Any graph is fully determined by its vertex count and edge set, so the list is a complete (if unindexed) representation.
Algorithms that process edges in a global order (by weight, or all edges per round) never need adjacency, so the list is not just sufficient but ideal for them.
Operations
| Operation | Description | Cost |
|---|---|---|
| addEdge(u, v, w) | Append a tuple. | O(1) |
| removeEdge(u, v) | Linear search and splice. | O(E) |
| hasEdge(u, v) | Linear scan. | O(E) |
| neighbors(u) | Scan every edge for endpoint u. | O(E) |
| sortByWeight | Standard sort. | O(E log E) |
| toAdjacencyList | One pass. | O(V + E) |
| degrees | One pass. | O(V + E) |
Recognition
How to tell a problem wants this.
- The input is given as
edges = [[u, v], …]— always the starting point. - Minimum spanning tree → sort edges → Kruskal.
- Negative weights or "at most k edges" → Bellman-Ford over the edge list.
- A question about a single edge (redundant connection, critical edge) is often a scan plus union-find.
Interactive demo
Play, step, change the input. ← → and space work too.
Showing the closely related Kruskal's Algorithm visualization.
| node | parent | root |
|---|---|---|
| A | A | A |
| B | B | B |
| C | C | C |
| D | D | D |
| E | E | E |
| F | F | F |
| G | G | G |
1sort edges by weight ascending2make_set(v) for every node3for (u, v, w) in edges:4 if find(u) != find(v):5 union(u, v); mst.add((u, v, w))6 else: skip — u and v already connected, edge would close a cycle7return mstPseudocode
1edges = [(u, v, w), ...]2kruskal(): sort edges by w; dsu = UnionFind(V); total = 03 for u, v, w in edges: if dsu.union(u, v): total += w; picked.append((u, v))4to_adj(): adj = [[]]*V; for u, v, w in edges: adj[u].append((v, w)); adj[v].append((u, w))Implementation
1from typing import NamedTuple2 3 4class Edge(NamedTuple):5 u: int6 v: int7 weight: int = 18 9 10class EdgeList:11 """The flattest representation: just the edges, in whatever order they12 came. Nothing is indexed, so it is the natural input format and the13 natural working set for algorithms that sort edges."""14 151 · State: one flat list of edges plus the vertex count16 def __init__(self, n: int) -> None:17 self.n = n18 self.edges: list[Edge] = []19 202 · Appending is O(1) and needs no per-vertex bookkeeping at all21 def add_edge(self, u: int, v: int, weight: int = 1) -> None:22 self.edges.append(Edge(u, v, weight))23 243 · Sorting by weight is the whole reason Kruskal prefers this form25 def sort_by_weight(self) -> None:26 self.edges.sort(key=lambda e: e.weight)27 284 · Any per-vertex query is O(E) — convert to lists if you need many29 def to_adjacency_list(self, directed: bool) -> list[list[int]]:30 adj: list[list[int]] = [[] for _ in range(self.n)]31 for e in self.edges:32 adj[e.u].append(e.v)33 if not directed:34 adj[e.v].append(e.u)35 return adj36 375 · Degree needs a full scan, which is the representation weak point38 def degree(self, u: int) -> int:39 return sum((e.u == u) + (e.v == u) for e in self.edges)class Edge(NamedTuple)gives an immutable, tuple-backed record with named fields,__eq__,__repr__and a default weight, all for three lines.self.edges.sort(key=lambda e: e.weight)uses a key function rather than a comparator, which is the Python convention and calls the key once per element.sum((e.u == u) + (e.v == u) for e in self.edges)relies onboolbeing anintsubclass, soTrue + Falseis 1 — compact and idiomatic.to_adjacency_listbuilds distinct rows with a comprehension, the fix for the[[]] * naliasing trap.- Because
Edgeis a tuple,sorted(self.edges)would order by(u, v, weight)lexicographically — occasionally useful, and worth knowing is the default.
NamedTuple instances are tuples, so they are noticeably smaller than equivalent class instances without __slots__.
NamedTupleis immutable and tuple-compatible;@dataclass(slots=True)is the mutable equivalent with similar memory characteristics.list.sort(key=...)is stable TimSort and evaluates the key once per element (a Schwartzian transform done for you); acmp_to_keycomparator is much slower.boolsubclassesint, which is why(e.u == u) + (e.v == u)counts endpoints without a conditional.operator.attrgetter("weight")is a slightly faster key than a lambda for large sorts, since it avoids a Python-level call.
- Trying to mutate a
NamedTuplefield (e.weight = 5), which raisesAttributeError— usee._replace(weight=5)or a dataclass. - Sorting with
sorted(edges)and getting lexicographic(u, v, weight)order when weight order was intended. - Building
to_adjacency_listwith[[]] * nand aliasing every row.
- Record types: C++ uses an aggregate
struct, TypeScript aninterface, Python aNamedTuple, and JavaScript a plain object literal — only Python gets immutability and value equality for free. - Sorting API: C++ and JS/TS take a comparator (
a < banda - brespectively — note the different conventions), while Python takes akeyprojection, which is both faster and harder to get wrong. - JavaScript is the only one whose default sort is actively wrong for this data: with no comparator it stringifies every edge object.
- Stability: Python
sortand JS/TSsort(since ES2019) are stable; C++std::sortis not, andstd::stable_sortmust be requested explicitly.
Complexity
| Operation | Average | Worst | Note |
|---|---|---|---|
| Access | O(1) | O(1) | Edge by index. |
| Search | O(E) | O(E) | Edge (u, v) or neighbors of u. |
| Insert | O(1) | O(1) | |
| Delete | O(E) | O(E) | |
| Update | O(E) | O(E) | O(1) if the index is known. |
| Neighbors | O(E) | O(E) | |
| Sort by weight | O(E log E) | O(E log E) | |
| Convert to adjacency list | O(V + E) | O(V + E) | |
| Space | O(E) | Plus O(V) if isolated vertices must be tracked. | |
Advantages & disadvantages
- Minimal memory: exactly
Erecords, no per-vertex overhead. - Trivial to sort, filter, or shuffle edges.
- Directly usable by Kruskal's Algorithm and Bellman-Ford.
- Easy to serialise and matches typical input formats.
- No neighbor access — traversals are
O(E)per vertex,O(VE)overall. - Edge lookup and deletion are
O(E). - Isolated vertices are invisible unless
Vis stored separately.
Use cases
- Kruskal's Algorithm minimum spanning tree.
- Bellman-Ford and Cheapest Flights Within K Stops.
- Redundant Connection: process edges in order with union-find.
- Counting degrees, finding the center of a star graph, town judge.
- Input parsing before building an Adjacency List.
- Kruskal's MST or any algorithm that sorts edges globally.
- Bellman-Ford and bounded-hop shortest paths.
- Single-pass edge statistics (degrees, duplicate detection, redundant edge).
- As the input format before converting.
- Any traversal (BFS, DFS, Dijkstra) — convert to an Adjacency List first.
- Frequent edge-existence queries — use an Adjacency Matrix or hash set of pairs.
- Graphs with isolated vertices where vertex enumeration matters — store
Vexplicitly.
Alternatives
Common mistakes
- Running BFS by scanning the edge list for every vertex —
O(VE). - Storing undirected edges twice and then double-counting weights in Kruskal.
- Forgetting that vertices with no edges do not appear in the list.
- Sorting in place when the original order matters (e.g. Redundant Connection needs input order).
Interview patterns
- Min Cost to Connect All Points: generate all
n(n-1)/2edges, sort, Kruskal. - Redundant Connection: iterate edges in input order with union-find; the first failing union is the answer.
- Cheapest Flights Within K Stops:
k + 1rounds of Bellman-Ford over the edge list with a copied distance array. - Find the Town Judge / Center of Star Graph: degree counting in one pass.
- Number of IslandsIntermediate
- Merge IntervalsIntermediate