Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Deep-copy a list whose nodes each have a next and an arbitrary random pointer. The clean answer maps each original node to its clone in a hash map, then wires up next and random on the clones by looking them up. O(n) time, O(n) space. The follow-up O(1)-space trick interleaves each clone right after its original, sets random from the neighbour, then unzips the two lists.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you handle arbitrary (possibly forward) references by separating 'create all nodes' from 'wire up pointers', and whether you null-safely resolve next/random.
Where candidates slip
Trying to set random in a single pass and dereferencing a clone that doesn't exist yet, or crashing on null random/next instead of mapping null to null.
How to stand out
Lead with the clean two-pass hash map, then offer the O(1)-space interleave-and-split as the follow-up, and stress restoring the original list when unzipping.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewDeep copy list with random pointersIs O(1) extra space required?NoYesExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2 · Optimal
Hash Map — Old Node → New Node (Optimal for Clarity)
1Hint 1Explore hintHide hint
The random pointer can point to a node you haven't copied yet. What if you created every clone first, before touching any pointers?
2Hint 2Explore hintHide hint
Pass 1: walk the list and for each original node make a bare clone (just the value), storing original → clone in a hash map.
3Hint 3Explore hintHide hint
Pass 2: walk again and for each original set clone.next = map[original.next] and clone.random = map[original.random], using a null-safe lookup so null maps to null.
A · Core idea
We sidestep the forward-reference problem by building all the nodes first and wiring their pointers second.
Why one pass fails. If you tried to set clone.random while creating clones left to right, original.random might point to a node further along that you haven't cloned yet — so there's no clone to point at. The fix is to decouple existence from linkage.
Pass 1 — create every clone. Walk the original list and, for each node, allocate a new node carrying the same val. Record the correspondence in a hash map: map[original] = clone. After this pass, every original node has a clone, regardless of where any random pointer leads.
Pass 2 — wire next and random. Walk the original list again. For each original, set map[original].next = map[original.next] and map[original].random = map[original.random]. Because all clones already exist, every lookup succeeds. Critically, use a null-safe lookup (map.get(...) returning null for a missing/null key) so that a null next or random becomes null in the clone instead of throwing.
Why it's fully independent. Every pointer in the new list is the result of a map lookup, so it always resolves to a clone — never to an original node. The original list is read but never mutated.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
A(random→C) → B(random→A) → C(random→null)Interactive diagram
The old→new mapping resolves random pointers
pass1 APass 1 fills the map; pass 2 reads original.random and writes the mapped clone.
A'(random→C') → B'(random→A') → C'(random→null)C · Code with teaching comments
D · Why it works and complexityO(n) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
The map converts 'I need the clone of an arbitrary original node' into an O(1) lookup, which is exactly what arbitrary cross-references demand. This is the identical strategy used to clone a general graph — the random pointer is just one extra edge per node.
ComplexityO(n) time, O(n) space — Two linear passes (create, then wire) → O(n) time. The map holds one entry per node → O(n) space, plus the n new nodes themselves.
E · Edge cases and pitfalls6head = null — Guard returns null immediately; the map is never built.Explore edge casesHide edge cases
Edge cases handled
head = nullGuard returns null immediately; the map is never built.single node with random → itselfmap has one entry; clone.random = map[node] = the clone itself, correctly self-referential.all random = nullPass 2 sets every clone.random to null via get(null) → null.
Pitfalls
- Indexing the map directly on a null key (map.get(null) is safe, but map[null] in some languages is not).
- Trying to set random during a single creation pass — the target clone may not exist yet.
- Returning the original head instead of oldToNew.get(head).
Approach 2 of 2
Interleave & Split — O(1) Extra Space
1Hint 1Explore hintHide hint
Instead of a map to find each clone, what if each clone lived right next to its original so you could reach it via original.next?
2Hint 2Explore hintHide hint
Pass 1: insert each clone immediately after its original (A → A' → B → B' → ...). Pass 2: set clone.random = original.random.next, since original.random.next IS the clone of original.random.
3Hint 3Explore hintHide hint
Pass 3: unzip the interleaved list back into two — restore the originals' next pointers AND extract the clones' next pointers.
A · Core idea
We eliminate the map by physically placing each clone next to its original, so 'the clone of X' is simply X.next.
Pass 1 — interleave. For each original node, create its clone and splice it directly after: clone.next = orig.next; orig.next = clone. The list becomes A → A' → B → B' → C → C'. Now every clone is reachable from its original in one hop.
Pass 2 — assign random pointers. For each original orig (stepping two at a time), the clone is orig.next. The clone's random should be the clone of orig.random, which — thanks to interleaving — is exactly orig.random.next. So orig.next.random = orig.random.next (guarding for orig.random == null). This is the elegant core: the map lookup became a .next.
Pass 3 — unzip. Now separate the two interleaved lists. Walk through and, for each original, set orig.next = orig.next.next (skip the clone) and clone.next = clone.next.next (skip the next original) — being careful not to dereference null at the tail. This both extracts the clone list AND restores the original list to its initial state, which is required: leaving the lists interleaved silently corrupts the input.
Why it's correct and independent. Every clone pointer is derived from a clone (*.next of an original gives a clone), so no clone references an original after the unzip. The price is the fiddly three-pass pointer surgery — powerful for the space win, but easy to botch under pressure.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
A(random→C) → B(random→A) → C(random→null)interleave
weave clones in
A→A'→B→B'→C→C'A'(random→C') → B'(random→A') → C'(random→null)C · Code with teaching comments
Reference implementation previewpublic Node copyRandomList(Node head) { if (head == null) return null; // Pass 1: interleave clones after originals for (Node cur = head; cur != null; cur = cur.next.next) {Explore the full codeHide the full code
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
Interleaving turns the hash map into adjacency: 'clone of X' becomes 'X.next'. It's a beautiful O(1)-space trick, but the unzip must restore the original list too — forgetting that is the most common way candidates get it marked wrong.
ComplexityO(n) time, O(1) space — Three linear passes (interleave, set random, unzip) → O(n) time. No hash map; only a few pointers are used → O(1) auxiliary space (the n new nodes are required output, not counted).
E · Edge cases and pitfalls6head = null — Guard returns null before any pass runs.Explore edge casesHide edge cases
Edge cases handled
head = nullGuard returns null before any pass runs.single node, random nullInterleave → A→A'; random null stays null; unzip restores A and returns A'.random pointing to head from a later nodeResolved via cur.random.next which is head's clone — interleaving handles forward and backward refs alike.
Pitfalls
- Forgetting to restore the original list's next in the unzip — corrupts the input.
- Dereferencing cur.random.next without the null check on cur.random.
- Mismatching the two-step advance and walking off the end of the interleaved list.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code preview/* // Definition for a Node. class Node { int val; Node next;Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Resolving random before the clone exists
random can point forward. If you create and fully wire a node in one pass, original.random may have no clone yet, giving null or a crash.
Explore the code comparisonHide the code comparison
Resolving random before the clone exists
random can point forward. If you create and fully wire a node in one pass, original.random may have no clone yet, giving null or a crash.
Don't write this
cur = head
while cur:
clone = Node(cur.val)
clone.random = old_to_new.get(cur.random) # cur.random not cloned yet!
old_to_new[cur] = clone
cur = cur.nextWrite this instead
cur = head
while cur: # pass 1: create all clones
old_to_new[cur] = Node(cur.val)
cur = cur.next
cur = head
while cur: # pass 2: wire pointers
old_to_new[cur].next = old_to_new.get(cur.next)
old_to_new[cur].random = old_to_new.get(cur.random)
cur = cur.next2Not restoring the original list when unzipping
The interleave method mutates the input. If you only extract clones and leave originals pointing at clones, you've corrupted the caller's list.
Explore the code comparisonHide the code comparison
Not restoring the original list when unzipping
The interleave method mutates the input. If you only extract clones and leave originals pointing at clones, you've corrupted the caller's list.
Don't write this
for (Node cur = head; cur != null; cur = cur.next.next) {
Node clone = cur.next;
copyTail.next = clone;
copyTail = clone;
// original still points at its clone!
}Write this instead
for (Node cur = head; cur != null; cur = cur.next) {
Node clone = cur.next;
cur.next = clone.next; // restore original
copyTail.next = clone;
copyTail = clone;
}Revise
Carry the pattern into the interview
Pattern to remember
Old→new hash map (or O(1) interleave-and-split)
Code pattern to memorize
old_to_new = {}
cur = head
while cur: old_to_new[cur] = Node(cur.val); cur = cur.next
cur = head
while cur:
old_to_new[cur].next = old_to_new.get(cur.next)
old_to_new[cur].random = old_to_new.get(cur.random)
cur = cur.next
return old_to_new.get(head)Rules to remember
- 1Map original node → clone node; the random pointers force you to resolve nodes you may not have created yet, so build all clones first.
- 2Use
map.get(...)/dict.get(...)so a null next or random maps to null, not a crash. - 3Two passes: pass 1 creates every clone, pass 2 wires next and random. One pass can't work because random can point forward.
- 4For O(1) space, interleave clone after original (A→A'→B→B'…), set
clone.random = orig.random.next, then carefully unzip. - 5When unzipping, restore the original list's next pointers too — leaving them interleaved is a silent corruption bug.
Use this pattern when
- Deep-copy any structure with arbitrary cross-references, not just sequential next pointers
- Cloning a graph-like object where you must map originals to copies (Clone Graph is the same idea)
- Any time you must resolve references to nodes that may not be constructed yet
- Serializing/duplicating linked structures with back- or forward-pointers
Choose another pattern when
- There is no random/cross pointer → a plain single-pass copy suffices
- You're allowed to share nodes (shallow copy) → no mapping needed
- The structure is acyclic and only points backward → you could resolve on the fly without a map
Interview takeaway
An old→new hash map is the universal deep-copy tool for structures with arbitrary pointers — the exact same trick clones graphs. The interleaving method is a neat O(1)-space flex, but the map version is the one to lead with for clarity.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternOld→new hash mapping for deep-copying structures with arbitrary references — the same approach clones graphs and trees-with-random-pointers. The interleave-and-split variant is the niche O(1)-space optimization to reach for only when memory is constrained.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Clone GraphComing soonSame old→new hash map, but each node has a list of neighbours instead of a single random pointer.
- Clone Binary Tree With Random PointerComing soonThe tree analogue — map originals to copies, then wire left/right/random.
- Add Two NumbersDifferent problem, but reinforces building a new list node-by-node with a dummy head.