Algorithm

Linked List Cycle II

Two Pointer Pattern

Linked List Cycle II

Given the head of a linked list, return the node where the cycle begins. If there is no cycle, return null. Do not modify the linked list.

CONSTRAINTS
  • The number of the nodes in the list is in the range [0, 10,000]
  • -100,000 <= Node.val <= 10,000
  • Do not modify the linked list bounds or values
EXAMPLE 1
Input: head = [3,2,0,-4], tail connects to index 1
Output: node at index 1 (value 2)
The last node (-4) links back to the node at index 1, so the loop begins at that node. That node is returned.
EXAMPLE 2
Input: head = [1,2], tail connects to index 0
Output: node at index 0 (value 1)
The loop starts at the head itself, so the head node is the entrance and is returned.
EXAMPLE 3
Input: head = [1], no cycle
Output: null
There is no loop, so there is no entrance node to return.
Return the node, its index, or its value?
Return the node object where the cycle starts. In the no-cycle case return null.
Is a cycle guaranteed to exist?
No — the list may be a plain straight list. Your solution must return null when the fast pointer reaches the end without a collision.
Can I modify the list, for instance to mark nodes?
No — the statement forbids modifying the list. The two-pointer method needs no modification and no extra memory.

We are given a singly linked list that contains a cycle, and our goal is to pinpoint the exact node where the cycle begins—the "doorway" of the infinite loop. If we simply traverse the list, we will be swept into the loop, running in circles forever without knowing which node originally sent us off course. How can we detect the start of a loop without allocating a massive memory bank to track every node we have already seen?

The Memory Breadcrumbs

The most straightforward approach is to write down the memory address of every node we encounter in a hash set. As we step through the list, we cross-reference each node with our records. The very first time we step onto a node that is already saved in our set, we have found our loop entry point.

While this is simple, it requires O(N) memory. If the list contains millions of nodes, keeping a massive bank of breadcrumbs in memory is extremely expensive.

python
# Store every visited node in a set
seen = set()
curr = head

while curr:
    if curr in seen:
        return curr  # This is the entry node!
    seen.add(curr)
    curr = curr.next

return None  # No cycle found.
The Pointer State: The Phase Pointers

To find the entrance using absolutely zero extra memory, we execute the search in two coordinated phases using four pointer states:
- Phase 1 (Collision Detection): We use slow (moves 1 node/step) and fast (moves 2 nodes/step).
- Phase 2 (Entrance Discovery): We use two identical-speed pointers, runner1 (starts at head) and runner2 (starts at the collision site). Both advance exactly one node at a time.

The Mathematical Loop Entry Symmetry

To understand why this works, we look at the mathematical symmetry of the track:
- Let A be the distance from the head to the loop entrance.
- Let B be the distance from the loop entrance to the collision site.
- Let C be the total length of the circular loop.

During Phase 1, slow covers a distance of A + B, while fast covers A + B + C (completing one full circle). Since fast runs exactly twice as fast as slow, we can set up a simple equation:

2 * (A + B) = A + B + C => A + B = C => A = C - B

This elegant equation proves that the distance from the start of the list to the loop entrance (A) is exactly equal to the remaining distance from the collision site to the entrance (C - B). Therefore, if we place one runner at the head and another at the collision site and march them forward at the exact same speed, they will inevitably meet right at the entrance!

Phase Alignment

We first run Phase 1 to detect a collision. If fast or fast.next becomes null, there is no loop and we return null. If they meet, we immediately halt the sprinter, place one pointer back at head, and march both pointers forward one step at a time until they meet.

python
slow = head
fast = head

# Phase 1: Detect collision
while fast and fast.next:
    slow = slow.next
    fast = fast.next.next
    if slow == fast:
        break
else:
    return None # Sprinter reached the end of a straight track, no cycle!

# Phase 2: Find the doorway
runner1 = head
runner2 = slow

while runner1 != runner2:
    runner1 = runner1.next # Move 1 step
    runner2 = runner2.next # Move 1 step

# Both meet at the loop entrance
return runner1
Empty Tracks and Starting-Line Loops

The phase-shift strategy handles boundary situations seamlessly:
- No Cycle Exists: The while...else block catches a null termination and exits safely before Phase 2 runs.
- Loop Starts at Head (e.g. Node 1 loops to Node 1): The collision occurs right at head. In Phase 2, runner1 and runner2 both start at head, the equality condition is instantly satisfied, and the loop is skipped, returning the correct entrance.

Worked Example:Finding the Entrance
3
2
0
-4
slow/fast
⟲ back to idx 1
Phase 1: 'slow' and 'fast' traverse the cyclic list and collide at the tail node containing -4.
3
runner1
2
0
-4
runner2
⟲ back to idx 1
Phase 2: We keep runner2 at the collision node (-4) and reset runner1 back to the head node (3). Both will now walk at identical speed.
3
2
runner1runner2
0
-4
⟲ back to idx 1
Both runners advance one step: runner1 moves to 2, and runner2 loops back from -4 to 2. They meet at node 2, which is the start of the cycle.
Interactive Strategy Visualization
CYCLE ORIGIN TRACKER

Geometric Convergence Algorithm

H
1
2
3
ENTRANCE
4
5
MEET
FAST
CYCLE DETECTED

Phase Details

Phase 1: Finding Collision

Use Slow (1x) and Fast (2x) pointers to detect the cycle. At Step 3, they collide at node 3.

Finding the Doorway

We use a beautiful geometric symmetry. After the pointers first touch, the distance from the very start of the list to the entrance is exactly the same as the distance from the meeting spot to the entrance.

Zero Extra Space

Most people use a "Seen nodes" list (Hash Set), but that takes extra memory. By using pure math and two pointers, we solve this with 0% extra space, which is the gold standard for top-tier coding interviews.

Strategy: Floyd's Tortoise and Hare

Time Complexity: O(N) | Space Complexity: O(1). The gold standard for detecting the start of a linked list cycle.

O(N) Time · O(N) Space Hash Set
O(N) Time · O(1) Space Two-Phase Fast/Slow