Union–Find (DSU)
Union-Find (also called Disjoint Set Union, or DSU) answers one question at silly speed: are these two items in the same group? It keeps items in separate sets and supports exactly two moves - union merges two sets, find tells you which set an item belongs to. While connections keep arriving - network cables, friendships, touching pixels - it tracks who is connected to whom without ever re-scanning the graph. With two tiny tricks, path compression and union by size, both moves cost almost O(1).
Watch the sets merge
Press play - or drag the timeline and step through it yourself.Eight items, each alone in its own set. Every node points at itself, so every node is a root (green) of size 1.
Complexity
School clubs and their captains
Every student starts as a one-person club, captain of themselves. When two clubs merge, the smaller club's captain agrees to follow the bigger club's captain - so most students never notice a thing. To learn your captain, you ask the person you follow, who asks the person they follow, up the chain until someone says "that's me". Two students are in the same club exactly when those chains end at the same captain. And here is the clever part: once you learn who the real captain is, you remember them directly - next time you ask, it is one step. That memory trick is path compression.
How it works, step by step
Start with every item as its own set: each item's parent pointer points at itself, which makes it a root.
find(x): follow parent pointers from x until you reach a node that points at itself. That root is the set's name.
union(x, y): find both roots. If they are the same root, x and y are already connected - do nothing (this is also how you detect a cycle).
Different roots? Attach one root under the other. Union by size: always hang the smaller tree under the bigger root, so no node in the bigger tree gets deeper.
Path compression: after every find, re-point each node you walked directly at the root. The tree flattens itself as you use it.
With both tricks, any sequence of operations costs O(α(n)) each on average - and α(n) is at most 4 for any input that fits in this universe.
The code, in JavaScript
The whole idea in one array: parent[i]. Follow pointers up to find the root; merging is a single write. Correct, but with no optimizations long chains can form.
function makeSets(n) {
// parent[i] === i means "i is a root"
return Array.from({ length: n }, (_, i) => i);
}
function find(parent, x) {
while (parent[x] !== x) x = parent[x]; // climb until a node points at itself
return x;
}
function union(parent, a, b) {
const ra = find(parent, a);
const rb = find(parent, b);
if (ra === rb) return false; // already in the same set
parent[ra] = rb; // hang one root under the other
return true;
}
const p = makeSets(6); // sets: {0} {1} {2} {3} {4} {5}
union(p, 0, 1); // {0,1}
union(p, 2, 3); // {2,3}
union(p, 1, 3); // {0,1,2,3}
find(p, 0) === find(p, 2); // → true (same root)
find(p, 0) === find(p, 4); // → false (4 is still alone)Dry run: 6 unions, 2 finds and a connected? check on 8 items
The same steps the visualizer plays, written as a table. Generated by running the real algorithm - not written by hand.
| Step | Operation | What happened |
|---|---|---|
| 1 | 1 | A tie in size, so either root could win - A goes under B. B is now the root of {A, B}, size 2. |
| 2 | 2 | D goes under C. The arrow means: D's parent is C. |
| 3 | 3 | E goes under F. Now three trees of size 2, plus the singles G and H. |
| 4 | 4 | Union by size: the smaller tree hangs under the bigger root. Hanging C's tree under G would have pushed D one level deeper - this way nobody gets deeper. |
| 5 | 5 | Size 1 vs size 3 - H goes under the bigger root C. C's set is now {C, D, G, H}, size 4. |
| 6 | 6 | B goes under F. Watch A: it is now two hops from its root (A → B → F). Ties are how depth sneaks in. |
| 7 | 7 | One hop: D → C. C is a root, so find(D) = C. Cheap - D already points straight at its root. |
| 8 | 8 | Hop 2: A → B → F - the root is F. That answer cost two hops. |
| 9 | 8 | Path compression: every node on the walk re-points at the root. A and B now point straight at F. |
| 10 | 9 | find(E) = F too. Same root - yes, A and E are connected, even though no union ever named them together. |
Good choice when…
- Edges keep arriving and you must answer "are these two connected?" between arrivals - network links, friend circles, merging user accounts.
- You are writing Kruskal's MST: DSU is the cycle check that decides whether an edge joins two different trees.
- Grouping equivalent things: connected regions of pixels in an image, equivalent variables, islands in a grid.
- You need the number of connected components and it must stay correct while the graph grows.
Bad choice when…
- Connections get deleted - DSU can only merge. Once two sets join, nothing un-joins them; fully dynamic connectivity needs a different (much harder) structure.
- You need the actual path between two nodes. DSU says "connected: yes or no", never how - that is a job for BFS or DFS.
- The graph is fixed and you ask the question once - a single BFS/DFS sweep is simpler and just as fast.
Common mistakes
- You need both optimizations. Skip them and a bad union order builds a chain, turning find into an O(n) crawl. Either trick alone gives O(log n); together they give O(α(n)).
- The famous "near O(1)" bound is amortized and only holds with the optimizations. α(n), the inverse Ackermann function, is at most 4 for any n that fits in physical reality - but a plain parent array earns none of that.
- DSU merges, it never splits. If your problem removes edges, do not reach for DSU by reflex - a common trick is to process removals backwards as additions, when the problem allows it.
- Caching roots is a classic stale-value bug. `find(a) === find(b)` is always safe at the moment you call it, but a root you saved in a variable can stop being a root after the very next union.
Union–Find (DSU) vs. its closest relatives
| Algorithm | Best | Average | Worst | Space | Stable |
|---|---|---|---|---|---|
| Union–Find (DSU)this page | O(1) | O(α(n)) | O(α(n)) | O(n) | - |
| Kruskal's MST | O(E log E) | O(E log E) | O(E log E) | O(V) | - |
| Breadth-First Search | O(V + E) | O(V + E) | O(V + E) | O(V) | - |
| Depth-First Search | O(V + E) | O(V + E) | O(V + E) | O(V) | - |