Start with the concrete trace below. It shows the state the algorithm must carry as it runs.
Problem trace
Number of Connected Components: Each unseen outer-loop vertex starts exactly one DFS and marks exactly one previously uncounted connected component.
Input and goalCount separate groups in an undirected graph.
Build the graph and initializeFor 6 nodes and edges [[0,1],[0,2],[3,4]], build both directions. Start with seen = {}, components = 0.
012
10
20
34
43
5none
queueempty
Start component 1 at node 0Outer-loop start 0 is unseen. Increment components 0->1, add 0 to seen, and seed stack [0].
012
10
20
34
43
5none
queue0
Expand node 0Pop 0; neighbors 1 and 2 are unseen, so add and push both. seen = {0,1,2}; stack = [1,2].
012
10
20
34
43
5none
queue12
Pop node 2LIFO order pops 2. Its only neighbor 0 is seen, so add nothing and leave stack [1].
012
10
20
34
43
5none
queue1
Finish component 1 at node 1Pop 1. Its only neighbor 0 is seen, so the stack empties with {0,1,2} marked.
012
10
20
34
43
5none
queueempty
Skip already seen startsOuter-loop starts 1 and 2 are in seen, so both take the continue branch and do not increment components.
012
10
20
34
43
5none
queueempty
Start component 2 at node 3Start 3 is unseen. Increment components 1->2, add 3, and seed stack [3].
012
10
20
34
43
5none
queue3
Discover node 4Pop 3 and inspect neighbor 4. Add unseen 4 and push it, producing seen {0,1,2,3,4} and stack [4].
012
10
20
34
43
5none
queue4
Finish component 2Pop 4; neighbor 3 is already seen, so the stack empties.
012
10
20
34
43
5none
queueempty
Skip outer start 4Outer-loop start 4 is already seen, so take the continue branch without changing components.
012
10
20
34
43
5none
queueempty
Start isolated component 3Start 5 is unseen, so increment components 2->3, add 5 to seen, and seed stack [5].
012
10
20
34
43
5none
queue5
Pop isolated node 5Node 5 has an empty adjacency list. Pop it without discoveries, leaving the stack empty.
012
10
20
34
43
5none
queueempty
Return three componentsThe outer loop has examined starts 0 through 5. Exactly three unseen starts launched floods, so return 3.
012
10
20
34
43
5none
queueempty
Recognize it
Use it when an undirected graph may contain multiple disconnected groups, including isolated vertices, and the task asks how many maximal reachable groups exist.
Keep true
After processing outer-loop starts below the current index, every vertex in their components is seen and components equals the number of DFS launches. A seen vertex can never launch another count.
Reuse it
A global visited set partitions a graph: every new search root contributes one component and consumes all vertices that must not contribute again. This transfers to islands, provinces, and cluster counting.
Read it this way: For 6 nodes and edges [[0,1],[0,2],[3,4]], build both directions. Start with seen = {}, components = 0. Step through the frames to watch the state change. The last frame shows the answer or the stopping condition.
Pattern: DFS from every unseen node.
Simple idea: Every unseen node starts one new component. DFS marks its full group, so no
node in that group starts another component.
def count_components(node_count: int, edges: list[list[int]]) -> int: graph = [[] for _ in range(node_count)] for first, second in edges: graph[first].append(second) graph[second].append(first) seen: set[int] = set() components = 0 for start in range(node_count): if start in seen: continue components += 1 seen.add(start) stack = [start] while stack: for neighbor in graph[stack.pop()]: if neighbor not in seen: seen.add(neighbor) stack.append(neighbor) return components