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 Alex Chen, 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.
Practice these live with InterviewChamp.AI
Drill Number of Connected Components in an Undirected Graph and other Cisco interview questions under real-loop conditions with instant feedback on your reasoning, complexity claims, and code.
Practice these live with InterviewChamp.AI →