Skip to content
Home DSA Clone a Linked List with Random Pointers: Deep Dive with All 3 Approaches

Clone a Linked List with Random Pointers: Deep Dive with All 3 Approaches

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Linked List → Topic 10 of 10
Clone a linked list with random pointers using HashMap, interweaving, or recursion.
🔥 Advanced — solid DSA foundation required
In this tutorial, you'll learn
Clone a linked list with random pointers using HashMap, interweaving, or recursion.
  • Two-pass hash map: first create all new nodes, then set next and random. O(n) time, O(n) space.
  • O(1) space via weaving: interleave clones into original, set randoms, then un-weave.
  • The hash map key is the original node pointer; the value is the cloned node.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer
  • Core challenge: a copied pointer still points into the original structure. You must remap every pointer to the corresponding clone.
  • HashMap approach (O(n) space): two passes — first create all clones, then wire next/random using the map.
  • Weaving approach (O(1) space): interleave clones between originals, set randoms via A.random.next, then un-weave.
  • Forward reference problem: when processing node A, A.random's clone may not exist yet. HashMap or weaving solves this.
  • Production risk: shallow copy (pointing random at original nodes) causes silent data corruption when the original is mutated.
  • This is a graph-traversal problem disguised as a linked-list problem — the random pointer breaks linearity.
🚨 START HERE
Linked List Clone Triage Cheat Sheet
Fast commands to diagnose common production issues with deep-copy operations.
🟡Original and clone share state — mutation in one affects the other.
Immediate ActionHeap dump to check if clone nodes reference original nodes via random pointers.
Commands
jcmd <pid> GC.heap_dump /tmp/heap.hprof
jmap -histo <pid> | grep Node (count Node instances — should be 2x original)
Fix NowIf clone nodes reference original nodes, the random-pointer copy is shallow. Replace with HashMap-based deep copy.
🟡NullPointerException in cloned list when accessing random pointers.
Immediate ActionCheck if the clone function has null guards for random pointers.
Commands
jcmd <pid> Thread.print (confirm stack trace location)
grep -A5 'random' src/ (find all random pointer assignments)
Fix NowAdd null check: if (original.random != null) clone.random = map.get(original.random); else clone.random = null;
🟡Heap grows after cloning but original should be GC-eligible.
Immediate ActionHeap dump to check if the HashMap from the clone operation still holds references to original nodes.
Commands
jcmd <pid> GC.heap_dump /tmp/heap.hprof
jmap -dump:format=b,file=/tmp/heap.hprof <pid>
Fix NowEnsure the HashMap is a local variable, not a class field. It should be eligible for GC after the clone method returns.
Production IncidentThe Undo Stack That Cross-Contaminated Editor SessionsA collaborative text editor's undo/redo system deep-copied the action history when a user duplicated a document. The clone function used a shallow copy for random pointers, causing undo operations in the duplicated document to corrupt the original document's action history.
SymptomAfter duplicating a document, undoing an action in the copy also reverted the same action in the original. The corruption was intermittent — it only occurred when random pointers in the action graph pointed to shared sub-structures (e.g., linked formatting spans).
AssumptionThe team initially suspected a shared-reference bug in the document model's serialization layer, because the corruption appeared only after duplication — not during normal editing.
Root causeThe clone function correctly deep-copied the next pointers (sequential action chain) but shallow-copied the random pointers (cross-references between non-adjacent actions). The random pointers in the cloned action graph still pointed to nodes in the original document's action graph. When the original document's actions were modified, the cloned document's random pointers followed those mutations, creating cross-contamination.
Fix1. Replaced shallow random-pointer copy with a HashMap-based deep copy: create all clone nodes first, then remap random pointers using the original-to-clone mapping. 2. Added a post-clone invariant check: verify that no node in the cloned graph references any node in the original graph. 3. Added integration tests that mutate the original after cloning and verify the clone is unaffected. 4. Introduced a CloneContext class that encapsulates the HashMap and provides getOrCreateClone() to handle forward references lazily.
Key Lesson
A deep copy must remap ALL pointers — not just next. Random/arbitrary pointers are the common oversight.Shallow copy of random pointers creates cross-contamination between a silent corruption bug — no exception, no crash, just wrong behavior.Post-clone invariant checks (no original nodes reachable from clone) catch shallow-copy bugs in debug builds.The forward-reference problem (random target not yet cloned) requires either pre-creation (HashMap) or structural interleaving (weaving).
Production Debug GuideSymptom-driven investigation paths for clone-related incidents.
Mutating the original structure also affects the cloned structure.Suspect shallow copy of random pointers. Check if random pointers in the clone reference nodes in the original. Add a post-clone invariant check.
NullPointerException when accessing random pointers in the cloned list.Check if the clone function handles null random pointers. A null random in the original must map to null in the clone — not a missing map entry.
Clone function returns a list with correct next pointers but wrong random pointers.Verify that the HashMap maps original nodes to clone nodes (not original to original). A common bug is using the original node as both key and value.
Infinite loop during cloning — thread spins at 100% CPU.Check if the weaving approach is used on a list where random pointers create cycles. The un-weave step may loop infinitely if the original list structure is corrupted.
Memory usage doubles after cloning — expected but check for leaks.Verify the HashMap is eligible for garbage collection after cloning. If the clone function stores the HashMap as a field, it prevents GC of all original nodes.

Deep-copying a linked list with random pointers is a pointer-remapping problem. Each node has two outgoing references: next (sequential) and random (arbitrary). A naive copy creates new nodes but leaves random pointers pointing at the original structure — a shallow copy that corrupts when the original is mutated.

The random pointer breaks the list's linearity. When processing node A, its random target (node 7) may not have been cloned yet. Two strategies solve this: a HashMap that pre-creates all clones before wiring pointers, or an interweaving trick that places each clone adjacent to its original so the clone is always reachable via original.next.

This problem appears in serialization of graph-like structures, deep-copying editor undo/redo trees, and duplicating compiler adjacency representations. In production, a shallow copy bug manifests as cross-contamination between independent data structures — one structure's mutation silently affects another.

What is Clone a Linked List with Random Pointers? — Plain English

Each node in this linked list has a next pointer (to the following node) and a random pointer (to any node in the list, or None). Cloning it naively requires two passes: one to create new nodes, and a second to set random pointers. A hash map from original_node -> cloned_node makes both passes easy and is the standard O(n) time, O(n) space approach.

The key challenge: when you create a new node and want to set its random pointer, the target of the random pointer may not have been created yet. The hash map solves this by building all new nodes first, then linking them.

Mental Model
The Photocopy Analogy
Why random pointers break naive copying
  • Photocopying a seating chart copies names in order — but the 'best friend' arrows still point at the original students.
  • You need a name-to-copy mapping so every arrow can be redirected to the photocopy.
  • Without the mapping, the photocopy is a shallow copy — it shares state with the original.
  • The HashMap is that name-to-copy mapping. The weaving trick achieves the same without explicit storage.
📊 Production Insight
The forward-reference problem is the core complexity. In a plain linked list, you process nodes sequentially and each node's next target is always the node you just created. With random pointers, the target may be anywhere — behind, ahead, or self approach (O(n-referencing. The HashMap pre-creation pass eliminates this by ensuring every clone exists before any pointer is wired.
🎯 Key Takeaway
Random pointers break linearity. The forward-reference problem requires either pre-creation (HashMap) or structural interleaving (weaving) to remap pointers correctly.

How Clone a Linked List with Random Pointers Works — Step by Step) time, O(n) space): 1. Pass 1 — create all new nodes: For each node in the original list: map[original] = Node(original.val). 2. Pass 2 — set pointers: For each node in the original list: map[node].next = map[node.next] (or None if node.next is None). map[node].random = map[node.random] (or None). 3. Return map[head]. O(1) space approach (weaving): 1. Insert each cloned node right after its original: A -> A' -> B -> B' -> ... 2. Set random pointers: A'.random = A.random.next (the clone of A's random). 3. Un-weave: restore original list and extract cloned list.

Hash map, making it unsafe for concurrent access.

🔥Why the Weaving Trick Works
  • After interleaving, every clone is exactly one hop from its original: original.next = clone.
  • So clone.random = original.random.next — the clone of the random target is always its next node.
  • No HashMap needed — the interleaved structure IS the mapping.
  • The un-weave step restores both lists by separating odd-positioned (original) and even-positioned (clone) nodes.
📊 Production Insight
The weaving approach temporarily mutates the original list by inserting clone nodes. In a concurrent environment, this mutation is visible to other threads — they see a list twice as long with interleaved clone nodes. If the original list is shared, use the HashMap approach to avoid mutation. The O(1) space saving is not worth a concurrency bug.
🎯 Key Takeaway
HashMap is the safe default — clean, debuggable, no mutation of the original. Weaving is the space-optimized alternative but mutates the original list temporarily
Choosing Between HashMap and Weaving
IfMemory is constrained (embedded systems, high-throughput services)
UseUse weaving — O(1) extra space. More complex but avoids HashMap allocation.
IfClarity is prioritized (prototyping, code review, teaching)
UseUse HashMap — two clean passes, easy to reason about, easy to debug.
IfInterview context — asked for O(1) space
UseImplement weaving. Mention the three-pass structure: interleave, set randoms, un-weave.
IfProduction code — must not corrupt the original list
UseUse HashMap. Weaving temporarily modifies the original list structure, which is unsafe if the original is concurrently accessed.
IfList may have null random pointers
UseBoth approaches handle null randoms. HashMap: map.get(null) returns null. Weaving: A.random.next would NPE — add null guard.

Worked Example — Tracing the Algorithm

List: 1 -> 2 -> 3 -> None. Random pointers: 1.random=3, 2.random=1, 3.random=2.

Pass 1 — create map: map[1] = Node(1), map[2] = Node(2), map[3] = Node(3).

Pass 2 — set pointers: map[1].next = map[2]. map[1].random = map[3]. map[2].next = map[3]. map[2].random = map[1]. map[3].next = None. map[3].random = map[2].

Cloned list: 1'->2'->3'. 1'.random=3', 2'.random=1', 3'.random=2'. Fully independent deep copy.

💡Pro Tip: Trace With Pointer Addresses
  • Print System.identityHashCode(original) and System.identityHashCode(clone.random).
  • If they match, clone.random points at the original — shallow copy bug.
  • If clone.random's hash matches map.get(original.random)'s hash, the copy is correct.
  • This works because identity hash is based on memory address, not value.
📊 Production Insight
The trace reveals the forward-reference resolution: when setting map[1].random = map[3], node 3's clone already exists because Pass 1 created all clones. Without Pass 1, you would need a lazy-creation mechanism (get-or-create) to handle the case where map[3] does not yet exist.
🎯 Key Takeaway
Tracing with identity hashes detects shallow-copy bugs instantly. The two-pass structure (create all, then wire all) eliminates forward-reference problems.

Implementation

The implementation uses two passes. In the first pass, iterate the original list and create a new node for each original node, storing the mapping in a hash map. In the second pass, use the hash map to wire up the next and random pointers on each new node. This runs in O(n) time and O(n) space. The alternative O(1) space approach interleaves cloned nodes between original nodes, sets random pointers, then splits the two lists apart in a third pass.

CloneLinkedListWithRandom.java · JAVA
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
package io.thecodeforge.list;

import java.util.HashMap;
import java.util.Map;

/**
 * Deep clone a linked list with random pointers.
 * HashMap approach: O(n) time, O(n) space.
 * Weaving approach: O(n) time, O(1) space.
 */
public class CloneLinkedListWithRandom {

    static class Node {
        int val;
        Node next;
        Node random;

        Node(int val) {
            this.val = val;
            this.next = null;
            this.random = null;
        }
    }

    // ─── Approach 1: HashMap (O(n) space) ──────────────────────────────────
    public static Node cloneWithHashMap(Node head) {
        if (head == null) return null;

        // Pass 1: create all clone nodes, store mapping original -> clone
        Map<Node, Node> cloneMap = new HashMap<>();
        Node current = head;
        while (current != null) {
            cloneMap.put(current, new Node(current.val));
            current = current.next;
        }

        // Pass 2: wire next and random pointers using the map
        current = head;
        while (current != null) {
            cloneMap.get(current).next = cloneMap.get(current.next);
            cloneMap.get(current).random = cloneMap.get(current.random);
            current = current.next;
        }

        return cloneMap.get(head);
    }

    // ─── Approach 2: Weaving (O(1) space) ──────────────────────────────────
    public static Node cloneWithWeaving(Node head) {
        if (head == null) return null;

        // Step 1: Interleave — insert clone after each original
        // Original: A -> B -> C
        // After:    A -> A' -> B -> B' -> C -> C'
        Node current = head;
        while (current != null) {
            Node clone = new Node(current.val);
            clone.next = current.next;
            current.next = clone;
            current = clone.next;
        }

        // Step 2: Set random pointers
        // A'.random = A.random.next (the clone of A's random target)
        current = head;
        while (current != null) {
            if (current.random != null) {
                current.next.random = current.random.next;
            }
            current = current.next.next;
        }

        // Step 3: Un-weave — separate original and clone lists
        Node cloneHead = head.next;
        current = head;
        while (current != null) {
            Node clone = current.next;
            current.next = clone.next;
            clone.next = (clone.next != null) ? clone.next.next : null;
            current = current.next;
        }

        return cloneHead;
    }

    // ─── Verification ──────────────────────────────────────────────────────
    public static boolean verifyClone(Node original, Node clone) {
        // Verify no original nodes are reachable from the clone
        // by checking identity hashes don't overlap
        Node origCurrent = original;
        Node cloneCurrent = clone;

        while (origCurrent != null && cloneCurrent != null) {
            if (origCurrent == cloneCurrent) return false; // same object
            if (cloneCurrent.random != null && cloneCurrent.random == origCurrent) return false;
            origCurrent = origCurrent.next;
            cloneCurrent = cloneCurrent.next;
        }
        return cloneCurrent == null && origCurrent == null; // same length
    }

    public static void main(String[] args) {
        // Build: 1 -> 2 -> 3
        // Random: 1->3, 2->1, 3->2
        Node n1 = new Node(1);
        Node n2 = new Node(2);
        Node n3 = new Node(3);
        n1.next = n2; n2.next = n3;
        n1.random = n3; n2.random = n1; n3.random = n2;

        // Clone with HashMap
        Node clone1 = cloneWithHashMap(n1);
        System.out.println("HashMap clone:");
        System.out.println("  Values: " + clone1.val + " -> " + clone1.next.val + " -> " + clone1.next.next.val);
        System.out.println("  Random: 1->" + clone1.random.val + ", 2->" + clone1.next.random.val + ", 3->" + clone1.next.next.random.val);
        System.out.println("  Independent: " + verifyClone(n1, clone1));
        System.out.println("  Identity diff: " + (clone1 != n1 && clone1.random != n1.random));

        // Clone with weaving
        Node clone2 = cloneWithWeaving(n1);
        System.out.println("\nWeaving clone:");
        System.out.println("  Values: " + clone2.val + " -> " + clone2.next.val + " -> " + clone2.next.next.val);
        System.out.println("  Random: 1->" + clone2.random.val + ", 2->" + clone2.next.random.val + ", 3->" + clone2.next.next.random.val);
        System.out.println("  Independent: " + verifyClone(n1, clone2));

        // Edge case: null list
        Node nullClone = cloneWithHashMap(null);
        System.out.println("\nNull list clone: " + (nullClone == null ? "null" : "ERROR"));

        // Edge case: single node with self-random
        Node self = new Node(42);
        self.random = self;
        Node selfClone = cloneWithHashMap(self);
        System.out.println("Self-random clone: val=" + selfClone.val + ", random->val=" + selfClone.random.val + ", isSameObject=" + (selfClone == selfClone.random));
    }
}
▶ Output
HashMap clone:
Values: 1 -> 2 -> 3
Random: 1->3, 2->1, 3->2
Independent: true
Identity diff: true

Weaving clone:
Values: 1 -> 2 -> 3
Random: 1->3, 2->1, 3->2
Independent: true

Null list clone: null
Self-random clone: val=42, random->val=42, isSameObject=true
⚠ Watch Out: Weaving Mutates the Original
The weaving approach temporarily inserts clone nodes into the original list. During Step 1 and Step 2, the original list is twice as long. If another thread reads the original list during cloning, it sees interleaved clone nodes. The HashMap approach never mutates the original — use it when the original is shared.
📊 Production Insight
The verifyClone() method — which checks that no original nodes are reachable from the clone — should be enabled in debug builds and integration tests. It catches shallow-copy bugs where random pointers still reference the original structure. This is the most common production bug in clone implementations.
🎯 Key Takeaway
Two approaches: HashMap (O(n) space, safe, debuggable) and weaving (O(1) space, mutates original, unsafe for concurrent access). The verifyClone() invariant check catches shallow-copy bugs. Always handle null random pointers explicitly.
Edge Cases to Handle
IfHead is null (empty list)
UseReturn null immediately. Both approaches handle this.
IfSingle node with random pointing to itself
UseHashMap: map[node].random = map[node] (points to its own clone). Weaving: clone.random = node.next = clone (after interleaving). Both correct.
IfAll random pointers are null
UseBoth approaches handle null randoms. The clone's random pointers are all null.
IfRandom pointers form a cycle (1->3, 3->1)
UseBoth approaches handle cycles. The HashMap approach never traverses random pointers — only next. Cycles are irrelevant.
IfRandom pointer points to a node not in the list
UseThis should not happen by problem definition. If it does, the HashMap returns null for that key, causing a NullPointerException. Add a null guard.
🗂 Three Approaches to Cloning a Linked List with Random Pointers
Trade-offs for space, safety, and complexity.
AspectHashMap (Two-Pass)Weaving (Interleave)Recursive Memoised
Time ComplexityO(n)O(n)O(n)
Space ComplexityO(n) — HashMap storageO(1) — no extra data structureO(n) — call stack + memo map
Mutates Original?NoYes — temporarily inserts clone nodesNo
Thread-Safe for Shared Original?Yes — original is read-onlyNo — original is modified during cloneYes — original is read-only
Code ComplexitySimple — two clean passesModerate — three passes with interleavingModerate — recursion with memoisation
Handles Cycles in Random?Yes — never traverses randomYes — never traverses randomRequires memo map to avoid infinite recursion
Handles Null Random?Yes — map.get(null) returns nullYes — with null guardYes — base case handles null
Stack Overflow RiskNoneNoneYes — deep recursion on long lists
DebuggabilityEasy — map contents are inspectableHarder — interleaved structure is confusingModerate — recursion trace helps
Interview PreferenceExpected as first answerExpected as O(1) space follow-upAcceptable but rarely asked

🎯 Key Takeaways

  • Two-pass hash map: first create all new nodes, then set next and random. O(n) time, O(n) space.
  • O(1) space via weaving: interleave clones into original, set randoms, then un-weave.
  • The hash map key is the original node pointer; the value is the cloned node.
  • Always check for None before dereferencing next and random to avoid NullPointerException.
  • This problem tests deep vs shallow copy understanding and pointer manipulation.

⚠ Common Mistakes to Avoid

    Shallow-copying random pointers
    Symptom

    clone.random points to the original node, not the clone. Mutating the original affects the clone. —

    Fix

    always remap random pointers through the HashMap: clone.random = map.get(original.random).

    Forgetting to handle null random pointers
    Symptom

    NullPointerException when original.random is null and you try map.get(original.random) without a null check —

    Fix

    map.get(null) returns null in Java, which is correct. But in the weaving approach, you must guard: if (current.random != null) clone.random = current.random.next.

    Using the original node as both key and value in the HashMap
    Symptom

    map.put(original, original) instead of map.put(original, new Node(original.val)). The 'clone' is the same object as the original —

    Fix

    always create a new Node with the same value. Verify with System.identityHashCode that original and clone are different objects.

    Not restoring the original list after weaving
    Symptom

    after cloning with weaving, the original list has interleaved clone nodes and is twice as long —

    Fix

    the un-weave step (Step 3) must restore the original list to its original state. If you skip this, the original is permanently corrupted.

    Storing the HashMap as a class field after cloning
    Symptom

    the HashMap holds references to all original nodes, preventing garbage collection of the original list —

    Fix

    make the HashMap a local variable inside the clone method. It goes out of scope when the method returns, allowing GC.

    No post-clone verification
    Symptom

    shallow-copy bug silently corrupts data for weeks before being detected —

    Fix

    add a verifyClone() method that checks no original nodes are reachable from the clone. Enable in debug builds and integration tests.

Interview Questions on This Topic

  • QWhat is the difference between a deep copy and a shallow copy in this context?
  • QHow does the O(1) space weaving approach work?
  • QWhat happens if you try to copy next and random pointers in a single pass without a hash map?
  • QWalk me through the weaving approach step by step. Why does A'.random = A.random.next work?
  • QHow would you verify that a clone is truly independent of the original?
  • QWhat happens if the random pointers form a cycle? Does either approach break?
  • QHow would you extend this to clone a graph (not just a linked list) where each node has arbitrary neighbor pointers?
  • QIf the original list is concurrently accessed by other threads, which approach is safe and why?

Frequently Asked Questions

What is the O(1) space approach?

Weave cloned nodes into the original list: A->A'->B->B'->C->C'. Set random: A'.random = A.random.next. Unweave: restore A->B->C and build A'->B'->C'. No hash map needed — the interleaved structure acts as the map. O(n) time, O(1) extra space.

Why can't you just copy next and random in one pass?

When you process node A and want to set A'.random to point to the clone of A.random, that clone might not exist yet. You need either a hash map (pre-create all clones first) or the weaving trick (interleave so that each node's clone is always its immediate next).

Does this algorithm work if random pointers form cycles?

Yes. The hash map approach creates the mapping first, then sets pointers. Cycles in random pointers don't cause infinite loops because we only traverse via the next pointer in both passes, not via random.

How is this different from cloning a plain linked list?

A plain linked list has only next pointers. Cloning it is a single-pass operation — create each clone and immediately set its next. The random pointer adds a second relationship that may point to any node (including forward references), requiring the HashMap or weaving technique.

Can Java's clone() method handle this?

No. Java's Object.clone() performs a shallow copy by default — it copies field references, not the objects they point to. For a linked list with random pointers, clone() would create new node objects but their next and random fields would still point to the original nodes. You must implement deep copy manually.

How do you verify a clone is correct?

Check three things: (1) the clone has the same values in the same order, (2) the clone's random pointers point to the correct clone nodes (not originals), and (3) no original node is reachable from the clone. Use System.identityHashCode to verify object identity.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousLRU Cache Implementation
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged