Clone Graph is the classic interview test of whether you really understand graph traversal and references. The trick is a single visited map that does double duty: it stops infinite loops and remembers each node's copy.
Problem. Given a reference to a node in a connected undirected graph, return a deep copy (clone) of the graph. Each node holds a value and a list of its neighbors. The copy must contain brand-new node objects with the same connections — no node may point back into the original graph.
Example: a 4-node graph A–B–C–D in a square with an extra diagonal A–C. The answer is a fresh graph
with copies A', B', C', D' wired the exact same way.
The slow way first
You might think to copy all the nodes first, then loop again to copy the edges. That works, but it needs two passes and some bookkeeping to match old nodes to new ones. And if you naively recurse into neighbors without remembering anything, you loop forever: A points to B, B points back to A, A points to B... a cycle the recursion never escapes.
The question to ask: how do I copy each node exactly once and still rebuild every edge?
The idea: one visited map, old → new
Do a single DFS. Keep a dictionary visited that maps an original node to its clone. When you reach a node:
- If it is already in
visited, return the copy you made earlier — do not clone it again. - Otherwise, make the copy, record it in
visitedbefore recursing, then walk its neighbors and attach each neighbor's clone.
Recording the copy before you recurse is what breaks the cycle: when the recursion comes back to a node it has already started, the lookup in step 1 hits and returns instantly.
The visited map is the whole solution: it is both the cycle-guard and the old-to-new translation table.
Walk through it
Step through the animation. The original graph sits on the left; the clone builds up on the right, node by node, as DFS visits. Watch the visited map fill: A, then B, then C, then D. When C reaches back to A, and when D reaches back to A, the node is already in visited — so no new node appears, we just draw the cloned edge using the copy we already made.
Pseudocode
clone(node):
if node in visited: # already cloned -> reuse it (breaks cycles)
return visited[node]
copy = new Node(node.value)
visited[node] = copy # record BEFORE recursing
for nbr in node.neighbors:
copy.neighbors.add( clone(nbr) ) # recursion reuses visited
return copyThe Python solution
def clone_graph(node, visited=None):
if visited is None:
visited = {}
if node in visited:
return visited[node]
# make the copy, record it BEFORE recursing
copy = Node(node.val)
visited[node] = copy
for nbr in node.neighbors:
if nbr not in visited:
copy.neighbors.append(clone_graph(nbr, visited))
else:
copy.neighbors.append(visited[nbr])
return copyvisitedmaps each original node → its clone. It is shared across the whole recursion.- The early
if node in visitedreturn is the cycle-guard: it stops us re-cloning a node we have already started. - Lines 7-8 are the heart: we create the copy and store it in
visitedbefore touching neighbors. - For each neighbor we either recurse (new node) or grab the already-made copy from
visited, then append it tocopy.neighbors.
Complexity
| Case | Time | Notes |
|---|---|---|
| Visit each node once | O(V) (moderate) | guarded by visited |
| Walk each edge once | O(E) (moderate) | appending neighbors |
| Total | O(V + E) (moderate) | standard DFS cost |
O(V) (moderate)We touch every node and every edge exactly once, so the work is O(V + E). The extra space is the visited map plus the recursion stack, both O(V).
When this pattern shows up
Whenever you copy or traverse a graph that may contain cycles, a hash map keyed by the original object is the move. The same idea powers deep-copying a linked list with random pointers, detecting cycles, and memoized DFS. The map is doing two jobs at once: avoid revisiting, and translate old references to new ones.
Record the clone in visited before you recurse into its neighbors. If you insert after the loop, a
cycle sends you back into a node whose copy does not exist yet, and you recurse forever.
Practice
When DFS reaches C and looks at C's neighbor A, A is already in visited. What happens — do we clone A again?
1. What is the purpose of the visited map in clone graph?
2. Why must we record the copy in visited BEFORE recursing into neighbors?
3. What is the time complexity of cloning a graph this way?
4. When DFS reaches a neighbor that is already in visited, what does it append?