323. Number of Connected Components in an Undirected Graph
mediumAsked at CiscoNumber of Connected Components at Cisco is the union-find warm-up. The interviewer is checking whether you reach for Union-Find with path compression and union-by-rank, OR a simpler DFS over an adjacency list. Both are valid; union-find is the answer they want when the problem later extends to dynamic edge additions.
By Sam K., Founder, InterviewChamp.AI · Last verified
Source citations
Public interview reports confirming this problem appears in Cisco loops.
- Glassdoor (2026-Q1)— Cisco SDE-II onsite reports cite this as a 30-minute union-find or DFS round.
- Blind (2025-11)— Cisco interview thread lists connected-components problems as a recurring theme.
Problem
You have a graph of n nodes. You are given an integer n and an array edges where edges[i] = [ai, bi] indicates that there is an edge between ai and bi in the graph. Return the number of connected components in the graph.
Constraints
1 <= n <= 20001 <= edges.length <= 5000edges[i].length == 20 <= ai <= bi < nai != biThere are no repeated edges.
Examples
Example 1
n = 5, edges = [[0,1],[1,2],[3,4]]2Explanation: Components: {0,1,2} and {3,4}.
Example 2
n = 5, edges = [[0,1],[1,2],[2,3],[3,4]]1Explanation: All five nodes are connected in a chain.
Approaches
1. DFS on adjacency list
Build adjacency list. For each unvisited node, DFS its component and increment the counter.
- Time
- O(V + E)
- Space
- O(V + E)
function countComponentsDFS(n, edges) {
const graph = Array.from({ length: n }, () => []);
for (const [a, b] of edges) {
graph[a].push(b);
graph[b].push(a);
}
const visited = new Array(n).fill(false);
let count = 0;
function dfs(node) {
visited[node] = true;
for (const nb of graph[node]) if (!visited[nb]) dfs(nb);
}
for (let i = 0; i < n; i++) {
if (!visited[i]) {
dfs(i);
count++;
}
}
return count;
}Tradeoff: Straightforward, linear in graph size. Use this when the graph is static and you just need the count. Recursion depth could overflow on a chain of 2000 — mention switching to BFS iteratively if pushed.
2. Union-Find with path compression + union-by-rank (optimal)
Initialize each node as its own component. For each edge, union the endpoints. Count distinct roots at the end.
- Time
- O((V + E) * alpha(n))
- Space
- O(V)
class UnionFind {
constructor(n) {
this.parent = Array.from({ length: n }, (_, i) => i);
this.rank = new Array(n).fill(0);
this.components = n;
}
find(x) {
if (this.parent[x] !== x) {
this.parent[x] = this.find(this.parent[x]);
}
return this.parent[x];
}
union(a, b) {
const ra = this.find(a), rb = this.find(b);
if (ra === rb) return false;
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]++;
}
this.components--;
return true;
}
}
function countComponents(n, edges) {
const uf = new UnionFind(n);
for (const [a, b] of edges) uf.union(a, b);
return uf.components;
}Tradeoff: Nearly-linear (alpha is the inverse Ackermann function, ~4 in practice). The big advantage over DFS: you can answer 'how many components' AFTER EACH NEW EDGE in nearly-O(1) — DFS would have to re-run the full traversal. Cisco interviewers love this when the follow-up is 'now process edges as a stream.'
Cisco-specific tips
Cisco interviewers grade this on whether you can pick the right tool. State BOTH options upfront: 'DFS if the graph is static, union-find if edges are added dynamically.' If they say 'just count once', DFS is simpler. If they say 'queries arrive over time', union-find is the answer. The union-by-rank + path-compression combo is the canonical optimization — name both out loud.
Common mistakes
- Forgetting to add the edge in BOTH directions in the adjacency list — DFS will visit only half the component.
- Implementing union-find without path compression — each find is O(n) worst case and you lose the asymptotic improvement.
- Forgetting that the answer is the count of DISTINCT roots after all unions — not the number of edges processed.
Follow-up questions
An interviewer at Cisco may pivot to one of these next:
- Graph Valid Tree (LC 261) — same union-find but also check for cycles AND check exactly n-1 edges.
- Friend Circles / Number of Provinces (LC 547) — same problem, matrix input instead of edge list.
- Accounts Merge (LC 721) — union-find with string keys.
- What if edges can be REMOVED? (Union-find doesn't support that; need link-cut trees or offline processing.)
Solve it now
Free. No sign-up. Python and JavaScript run instantly in your browser.
FAQ
Union-find or DFS — which to choose?
DFS for static graphs (one count query at the end). Union-find for dynamic graphs where edges arrive over time and you need the count after each addition. For this specific LeetCode problem (static graph, single query), both work; union-find is the more common 'production' answer Cisco interviewers expect.
Why path compression + union-by-rank?
Together they give nearly-O(1) per operation (amortized O(alpha(n)), where alpha is the inverse Ackermann function — effectively constant). Without them, find can degenerate to O(n) on a worst-case chain. Cisco interviewers expect both optimizations and will ask 'why both?' if you only mention one.
Free learning resources
Curated free links for this problem.