The Union-Find With Rank I Copy Into Every Contest

After timing out on a Codeforces 'connected components' problem twice, I memorized this 25-line union-find with path compression and union-by-rank. The find() is iterative (no recursion to blow the stack) and component_count is O(1).

JavaScript
Frontend
3 snippets
union-find
path-compression
graphs
code-template
emmadiallo

By @emmadiallo

April 30, 2026

·

Updated May 20, 2026

895 views

22

4.3 (11)

// DSU (Disjoint Set Union) / Union-Find. The two optimizations that take the
// per-operation cost from O(n) to amortized O(alpha(n)) (effectively O(1)):
//   1. Path compression in find(): every traversal flattens the chain.
//   2. Union-by-rank: attach the shorter tree under the taller one.
// Iterative find() avoids stack overflow on chains of 1e6 nodes.

class DSU {
    constructor(n) {
        this.parent = new Array(n);
        this.rank = new Array(n).fill(0);
        this.componentCount = n;
        for (let i = 0; i < n; i++) this.parent[i] = i;
    }

    find(x) {
        // Two-pass: first walk to the root, then point every node along the path at the root.
        let root = x;
        while (this.parent[root] !== root) root = this.parent[root];
        let cur = x;
        while (this.parent[cur] !== root) {
            const next = this.parent[cur];
            this.parent[cur] = root;
            cur = next;
        }
        return root;
    }

    union(a, b) {
        const ra = this.find(a);
        const rb = this.find(b);
        if (ra === rb) return false;  // already connected
        if (this.rank[ra] < this.rank[rb]) {
            this.parent[ra] = rb;
        } else if (this.rank[ra] > this.rank[rb]) {
            this.parent[rb] = ra;
        } else {
            this.parent[rb] = ra;
            this.rank[ra] += 1;
        }
        this.componentCount -= 1;
        return true;
    }

    connected(a, b) { return this.find(a) === this.find(b); }
}

const dsu = new DSU(6);
console.log('start:', dsu.componentCount);  // 6
dsu.union(0, 1);
dsu.union(2, 3);
dsu.union(4, 5);
console.log('after 3 unions:', dsu.componentCount);  // 3
dsu.union(1, 3);
dsu.union(3, 5);
console.log('all connected:', dsu.componentCount);   // 1
console.log('connected(0, 5)?', dsu.connected(0, 5));  // true
console.log('union(0, 5) returns:', dsu.union(0, 5));  // false (already connected)

The two-pass iterative find is the part I tweak between contests. The first pass walks parent pointers up to the root; the second pass goes back and rewrites every pointer along the path to point directly at the root. This is path compression that flattens the chain so future find calls are O(1). The recursive version is shorter (return parent[x] === x ? x : parent[x] = find(parent[x])) but blows the stack on adversarial chains around a million nodes, which is the contest input size where I started getting WAs. Tracking componentCount and decrementing on a successful union saves you from doing a full Set build at the end of every problem.