Foundation · Module 10 · Lesson 4

Fast & slow pointers

Middle node, cycles, and cycle entry.

15–25 min readLesson 56 of 123

Lesson 56 of 123

1d streak

Tortoise and Hare Mechanics: Midpoint & Boundary Parity

The two-pointer technique known as the Tortoise and Hare advances two reference cursors through a list at different velocities: slow steps 1 node per iteration while fast steps 2 nodes per iteration (fast = fast.next.next). This 2:1 velocity differential allows single-pass inspection of structural properties without pre-computing list length.

Architectural Boundary · Do Not Confuse

Concept A

Right-Biased Midpoint: while (fast != null && fast.next != null)

Concept B

Left-Biased Midpoint: while (fast.next != null && fast.next.next != null)

For an even list [1, 2, 3, 4], the right-biased condition terminates with slow at node 3 (second middle). The left-biased condition terminates with slow at node 2 (first middle), which is essential when splitting a list into two independent halves for Merge Sort.

List LengthList ValuesLoop ConditionFinal slow PositionFinal fast Position
Odd (5)[1, 2, 3, 4, 5]fast != null && fast.next != nullNode(3) (Exact Middle)Node(5) (fast.next == null)
Even (4)[1, 2, 3, 4]fast != null && fast.next != nullNode(3) (Second Middle)null (fast == null)
Even (4)[1, 2, 3, 4]fast.next != null && fast.next.next != nullNode(2) (First Middle)Node(3) (fast.next.next == null)
FindMiddleNode.java
// Finding Left Middle for Clean Bisecting
public ListNode getLeftMiddle(ListNode head) {
    if (head == null) return null;
    ListNode slow = head;
    ListNode fast = head;

    // Terminates with slow at first middle for even lengths
    while (fast.next != null && fast.next.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }

    return slow;
}

Active Recall Checkpoint

Why does dividing a linked list for Merge Sort strictly require the left-biased middle instead of the right-biased middle?

Show answer & explanation

If an even list of 2 nodes [1, 2] uses the right-biased middle, slow lands on node 2. Splitting at slow.next creates halves of length 2 and 0, resulting in infinite recursive splitting and a StackOverflowError.

Floyd’s Cycle Detection: Mathematical Proof of Convergence

Floyd’s Cycle-Finding Algorithm determines whether a linked list contains a cycle using strictly O(1) auxiliary memory. Rather than maintaining a hash set of visited memory addresses (which consumes O(n) heap memory), the 2:1 runner speed difference mathematically guarantees detection.

  1. 01Phase 1: Both pointers enter the cycle. Let the cycle have length L.
  2. 02Phase 2: Once slow enters the cycle, fast is already somewhere inside the cycle.
  3. 03Phase 3: Consider the relative distance d between fast and slow measured along the direction of traversal (0 <= d < L).
  4. 04Phase 4: In every discrete time step, slow moves +1 and fast moves +2. The relative distance decreases by (2 - 1) = 1 step per tick modulo L.
  5. 05Phase 5: Because the gap closes by strictly 1 unit each step, the distance must hit exactly 0 mod L in at most L iterations. Fast can never hop over slow without colliding.

Maximum Iterations Bound

Let K be the distance from head to the cycle start node, and L be the cycle length. slow enters the cycle after K steps. At that moment, fast has taken 2K steps and is at position K mod L inside the cycle. The distance between them is at most L - 1. Therefore, slow and fast will collide after at most K + L steps. Overall runtime is strictly linear O(n).
HasCycle.java
public boolean hasCycle(ListNode head) {
    if (head == null || head.next == null) return false;

    ListNode slow = head;
    ListNode fast = head;

    while (fast != null && fast.next != null) {
        slow = slow.next;         // 1 step
        fast = fast.next.next;    // 2 steps

        if (slow == fast) {
            return true; // Collision confirms cycle existence
        }
    }

    return false; // Fast reached terminal null: linear chain
}

Active Recall Checkpoint

If fast moved 3 steps per tick instead of 2, would cycle collision still be guaranteed?

Show answer & explanation

Not unconditionally in a single cycle revolution. With speed 3, the relative distance closes by (3 - 1) = 2 steps per tick. In an odd-length cycle, if the initial gap is odd, fast could skip over slow. While it would eventually collide after multiple loop circuits if gcd(2, L) divides the gap, speed 2 is optimal because step difference 1 guarantees collision within at most L steps without modular skipping.

Finding the Cycle Start Node: Mathematical Derivation

Once Floyd’s algorithm detects a collision between slow and fast, we can locate the exact starting node of the cycle in O(n) time and O(1) space using an elegant mathematical symmetry.

Segment VariableDefinition / Geometric Meaning
aDistance from list head to the cycle entrance node
bDistance from cycle entrance node to collision meeting point
cDistance from collision meeting point back to cycle entrance node
L = b + cTotal perimeter length of the cycle
  1. 01At collision: distance(slow) = a + b.
  2. 02At collision: distance(fast) = a + b + n * L (where n >= 1 is the number of full cycle loops completed by fast).
  3. 03Since fast moves at twice the speed of slow: distance(fast) = 2 * distance(slow).
  4. 04Substitute: a + b + n * L = 2 * (a + b) => a + b + n * L = 2a + 2b.
  5. 05Simplify: a = n * L - b.
  6. 06Decompose n * L: Since L = b + c, we rewrite n * L - b = (n - 1) * L + (L - b) = (n - 1) * L + c.
  7. 07Conclusion: a = (n - 1) * L + c. The distance from the list head to the cycle entrance (a) equals the distance from the collision point to the cycle entrance (c), plus (n - 1) full cycle revolutions!
DetectCycle.java
public ListNode detectCycle(ListNode head) {
    if (head == null || head.next == null) return null;

    ListNode slow = head;
    ListNode fast = head;

    // 1. Detect collision
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow == fast) {
            // 2. Derive cycle start: reset ptr1 to head, keep ptr2 at collision
            ListNode ptr1 = head;
            ListNode ptr2 = slow;

            // Both step at 1 node per iteration until meeting at entrance
            while (ptr1 != ptr2) {
                ptr1 = ptr1.next;
                ptr2 = ptr2.next;
            }
            return ptr1; // Cycle entrance node
        }
    }

    return null; // No cycle
}

Use it when

Use Floyd’s cycle detection and entry derivation when analyzing pointer graphs with O(1) space constraints, detecting infinite redirects in state transitions, or solving array duplicate problems (e.g., LeetCode 287 Find the Duplicate Number).

Reconsider when

Use a HashSet of visited identifiers if nodes lack distinct references or if modifying memory during detection is acceptable.

Cost Model

Complexity & Trade-offs

Traversal
O(n)
O(1)
Pointer algorithms usually scan nodes once with constant auxiliary state.

When to use it

  • Frequent local insertions, deletions, chain rewiring, and cycle analysis.
  • Problems where contiguous storage or random access is unnecessary.
  • In-place sequence transformations.

Common mistakes

  • Losing the unread suffix by overwriting next too early.
  • Failing to update head or tail after a boundary mutation.
  • Dereferencing null or looping forever on a cycle.

Practice prompts

  • Reproduce the worked Fast & slow pointers example without looking at the trace.
  • Change one input condition and explain whether the Fast & slow pointers invariant still holds.
  • Implement the approach, derive its cost, and compare it with a straightforward baseline.

Mastery check

Can you derive it instead of memorizing it?

State the invariant before looking at code.
Explain why every transition is safe.
Construct a worst-case input.
Name one alternative and its trade-off.