</> MAANG.io
coding interview · 201

Intermediate

Advanced coding interview preparation covering complex algorithms, system design, and optimization techniques for senior-level positions.

0/170 solved 0% complete

Pointer Techniques

What is this?

Two runners set off around a track, one at twice the speed of the other. If the track is a straight line, the fast runner finishes and the question is over. If it loops, the fast runner must eventually lap the slow one — they meet, and the meeting itself proves the loop exists. No map of the track, no marking the ground, no memory of where you have been.

That is the first half of this chapter. The second half is a different kind of cleverness: when you need to remember a mapping from every old node to its copy, and are not allowed a hash map, you can build the mapping into the list itself.

flowchart TD A["Only a local view, O(1) space"] --> B["Two speeds"] A --> C["Restructure, then restore"] B --> B1["slow += 1, fast += 2"] B1 --> B2["they meet ⟺ a cycle exists"] B2 --> B3["reset one to head, step both
→ they meet at the loop entry"] C --> C1["weave each copy behind its source"] C1 --> C2["copy.random = node.random.next"] C2 --> C3["unweave into two clean lists"]

💡 Fun fact: why does resetting one pointer to the head find the loop's entrance? Let a be the distance from head to entry and b the distance from entry to the meeting point. When they meet, the slow pointer has walked a + b and the fast one exactly twice that — so the extra distance the fast pointer covered is a whole number of loops. That forces a to equal the remaining distance from the meeting point back around to the entry. Two pointers stepping one at a time from the head and from the meeting point therefore arrive together, at the entry.

🔓 The 2 problems in this chapter are free. Sign in with Google or Microsoft to start solving.


The one-line idea: with one successor per node and no spare memory, you buy global knowledge either by running two pointers at different rates or by temporarily rewriting the structure so the information you need becomes locally reachable — then restoring it.


1. Two speeds

slow = fast = head
while fast and fast.next:
    slow = slow.next
    fast = fast.next.next
    if slow is fast:
        return True          # they can only meet inside a loop
return False                 # fast fell off the end

The loop condition is the part to get right: fast and fast.next, because the fast pointer takes two steps and either could walk off the end. Checking only fast crashes on even-length lists.

The same skeleton, retuned, gives you the middle node (when fast reaches the end, slow is halfway) and the k-th from the end (start fast k nodes ahead, then advance both until fast hits the end). One pass each, no length precomputation.

2. Interleave instead of mapping

To deep-copy a list whose nodes carry an arbitrary random pointer, the obvious solution maps each original node to its copy — O(n) space. The trick removes the map by making each copy findable from its source:

pass 1 — weave:      A → A' → B → B' → C → C'
pass 2 — randoms:    A'.random = A.random.next     ← the copy of whatever A pointed at
pass 3 — unweave:    A → B → C   and   A' → B' → C'

Pass 2 is the whole idea: because every copy sits immediately behind its original, "the copy of node X" is always X.next. The map is not gone — it is encoded in the list's own shape.

Pass 3 must restore the original list exactly. Leaving it interleaved is a correctness failure, not a tidiness one: the caller still holds a reference to it.


3. A 30-second worked example (finding the loop entry)

1 → 2 → 3 → 4 → 5
        ↑       |
        └───────┘        entry is node 3, loop length 3

step  slow  fast
 1     2     3
 2     3     5
 3     4     4      ← meet at node 4

reset one pointer to head, advance both by one:
       1     4
       2     5
       3     3      ← meet at node 3, the entry ✓

Distance head→entry is 2, and distance meeting-point→entry going forward around the loop is also 2. That equality is exactly what the fun-fact argument above proves.


4. Where you'll actually meet this

  • Dependency and build systems. Detecting import cycles or circular module dependencies, where the graph is walked rather than stored.
  • Pseudo-random generators. Finding a generator's period is cycle detection on the sequence it produces — Floyd's original motivation, and still how PRNG quality is measured.
  • Pollard's rho factorisation. The same tortoise-and-hare, applied to a numeric sequence, is a practical integer-factorisation method.
  • Filesystem traversal. Symlink loops must be detected without holding every visited inode in memory on constrained systems.
  • Serialisation and ORMs. Deep-copying an object graph with arbitrary cross-references is Copy List with Random Pointer at production scale — usually with a map, but the constraint version teaches why the map was needed.

5. Problems in this chapter

▶ Linked List Cycle

Determine whether a list contains a loop, in O(1) space. Floyd's tortoise and hare, and the follow-up — where does the loop start — is where the interesting conversation begins.
Pattern: two speeds. Target: O(n) time, O(1) space (versus the O(n)-space hash-set baseline).

▶ Copy List with Random Pointer

Deep-copy a list where each node also points at an arbitrary node. Weave the copies in, resolve the random pointers locally, then unweave.
Pattern: restructure-then-restore. Target: O(n) time, O(1) extra space (versus the O(n)-space map).


6. Common pitfalls 🚫

  • Checking only fast in the loop guard. You need fast and fast.next — the two-step advance can fall off either node.
  • Starting the pointers one apart "to avoid the immediate match". Start both at the head and advance before comparing; offsetting them makes the entry-point proof stop working.
  • Forgetting to unweave. The original list must be returned to its exact prior shape, since the caller still references it.
  • Handling random = None carelessly. node.random.next dereferences null when random is unset; guard it explicitly.
  • Assuming the loop starts at the head. The meeting point is not the entry, and the entry is rarely the head — the reset step is required.
  • Reaching for a hash set and stopping there. It is a correct O(n)-space answer and the right thing to say first, but the follow-up is always "now do it in O(1)".

7. Key takeaways

  1. Two pointers at different speeds reveal structure neither could see alone — cycle existence, the middle, the k-th from the end.
  2. Meeting proves a loop; a reset finds its entry. Know the argument, not just the steps.
  3. Guard both fast and fast.next.
  4. Restructuring can replace a hash map. Interleaving makes "the copy of X" reachable as X.next.
  5. Restore what you rearranged. A temporarily broken input must be intact when you return.
  6. Why interviewers like it: the code is ten lines, so the entire signal is in your edge-case handling and whether you can explain why the pointers meet where they do.

Order: Linked List Cycle → Copy List with Random Pointer.

Linked List Cycle II

Core idea: A singly linked list is a functional chain — each node points to exactly one successor — so following next either runs off the end (None) or loops forever. Run a slow pointer (+1) and a fast pointer (+2): they meet inside the cycle iff a cycle exists. Then comes the elegant part — reset one pointer to the head and advance both by 1; they collide exactly at the cycle's entry node. The reason is pure arithmetic: the distance from the head to the entry equals the distance from the meeting point to the entry. All of it in O(1) extra space.

Problem, rephrased

You're handed the head of a singly linked list. Somewhere a node's next pointer might secretly point back to an earlier node, splicing the tail into a loop — like a train track that, instead of ending at a station, curves around and rejoins itself partway down the line.

Your job (LeetCode 142): return the node where the cycle begins — the first node the tail loops back into. If there is no cycle (the track ends at a buffer, None), return None.

You must not modify the list, and the senior-level expectation is O(1) extra memory.

Input list Cycle? Output Why
3 → 2 → 0 → -4, tail → node 2 yes, enters at the 2 node node with val 2 the -4 node's next points back to 2
1 → 2, tail → node 1 yes, enters at the head node with val 1 2's next points back to the head
1, no cycle (next = None) no None a single node ending in None
1 → 2 → 3 → 4, all next forward no None the chain terminates at None

The single move — "advance to node.next" — is the only operation. Everything below is about following next at two speeds and noticing where the pointers land.

Sign in to continue reading

The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.

Sign in to read the full lesson

Sign in to MAANG.io

Use your Google or Microsoft account — no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.