bigonotes
DSA: Patterns & PythonLinked List

Fast/Slow & Gap Pointers

Use two pointers at different speeds or a fixed distance apart to find cycles, cycle entries, middles and the k-th node from the end.

7 min readLast updated

A singly linked list only lets you walk forward, and you usually don't know its length. Two pointers get around both limits without extra memory. Move them at different speeds (slow 1 step, fast 2 steps) to detect a loop or find the middle. Move them at the same speed with a fixed gap to find the k-th node from the end in one pass.

The idea#

A linked list has no index, so you cannot jump to the middle or count back from the end. Two pointers that move at different speeds, or stay a fixed distance apart, give you that position in one pass.

Tortoise and hare#

  • Cycle detection: if the list has a loop, fast eventually laps slow and they land on the same node. If there is no loop, fast reaches None.
  • Middle: fast covers twice the distance, so when fast reaches the end, slow is halfway.

Why must they meet? Once both pointers are inside the loop, measure the distance from fast forward to slow. Each step slow moves 1 and fast moves 2, so the gap shrinks by exactly 1. It can't jump over zero, so they meet. The gap starts below the loop length, so this takes at most one lap after slow enters: O(n)O(n) overall, O(1)O(1) space.

Finding where the cycle starts (Floyd)#

Knowing there is a loop is often only half the job. The meeting point is not in general where the loop starts. To find the entry, reset one pointer to head and move both one step at a time. Where they meet now is the entry.

A tail of a steps from head to the cycle entry, then b steps around the cycle of length c to the meeting point; since a + b is a whole number of laps, walking a more steps from the meeting point reaches the entry

Why does this work? Let a be the steps from head to the entry, b the steps from the entry to the meeting point, and c the loop length.

  1. When they meet, slow has walked a + b and fast has walked 2(a + b).
  2. Fast's extra distance, a + b, was spent going around the loop, so it is a whole number of laps: a + b = k * c.
  3. So a = k * c - b. Starting at the meeting point (which is b past the entry), walking a steps means walking k * c - b. That lands exactly on the entry.
  4. A pointer starting at head also reaches the entry after a steps. Both arrive together.

Gap pointers#

The second setup keeps both pointers at the same speed and changes only where they start. To find the k-th node from the end, give right a head start of k nodes, then move both together. When right falls off the end, left is k nodes behind it. Start left at a dummy node and it stops just before the target, which is what you need to delete it.

How to spot it#

Each signal maps to one pointer setup.

  • "Is there a loop?", "where does the loop start?": fast and slow.
  • "Find the middle", "split the list in half": fast and slow.
  • "k-th node from the end", "remove the n-th from the end": gap pointers. Move right first, then both.
  • "Where do two lists meet?": align the two pointers so they have the same distance left.
  • An array whose values are valid indices (i -> nums[i]) behaves like a linked list, so cycle tricks apply.

Template#

This is Floyd's cycle detection with the entry point. Both pointers must start at head for the a + b argument to hold.

slow = head
fast = head
while fast is not None and fast.next is not None:
    slow moves 1 step; fast moves 2 steps
    if slow is fast:                 # phase 1: there is a cycle
        slow = head                  # phase 2: reset one pointer
        while slow is not fast:
            slow moves 1 step; fast moves 1 step
        return slow                  # they meet at the entry
return None                          # fast fell off: no cycle

Gap pointers shape#

The gap version from the idea above walks one pointer ahead, then moves both at the same speed.

dummy = new node whose next is head
left = dummy
right = head
repeat k times: right = right.next   # right gets a k-node head start
while right is not None:
    left = left.next; right = right.next
left now sits just before the k-th node from the end

Which middle?#

Finding the middle uses the same fast and slow loop, but an even-length list has two middles. The start position of fast decides which one you get.

Start Loop while Even length: slow ends on
slow = fast = head fast and fast.next the second middle
slow = head, fast = head.next fast and fast.next the first middle

Use the first-middle version when you split a list for merge sort: a two-node list then splits 1 + 1 (see Merging & Sorting Lists).

Pitfalls#

These mistakes cause most wrong answers and infinite loops.

  • Thinking the meeting point is the loop start. It is only where fast caught slow. The entry needs the reset-and-walk phase.
  • Starting fast one step ahead for Floyd. fast = head.next is fine for detecting a cycle. It breaks the a + b bookkeeping, though, and phase 2 can loop forever. Start both at head when you need the entry.
  • Checking fast.next.next without checking fast.next. The loop condition must be fast and fast.next.
  • Comparing values instead of nodes. Use is for node identity; different nodes can hold equal values.
  • Gap off by one. Starting left at dummy (not head) leaves it before the target, ready to unlink.

Problems in this pattern#

These problems use this pattern. Solve them before you open the hints below. Start with the first two.

Hints#

Open a hint only after you have tried the problem for a while.

Hint: Linked List Cycle

You only need phase 1. Return true as soon as slow is fast, and false when fast falls off the end.

Hint: Middle of the Linked List

Check the problem's rule for even lengths. It wants the second middle, so start both pointers at head.

Hint: Intersection of Two Linked Lists

If both lists had the same length, walking in step would meet at the shared node. Either count both lengths and advance the longer list by the difference, or switch heads. With switching, a walks A then B, and b walks B then A. Each walks len(A) + len(B) nodes, so they reach the shared node, or None, at the same moment.

Hint: Linked List Cycle II

Run both phases of the template. Both pointers must start at head, or phase 2 can loop forever.

Hint: Remove Nth Node From End of List

The head might be the node you remove, so start left at a dummy. Give right an n-node head start from head. When right falls off, unlink with left.next = left.next.next and return dummy.next.

Hint: Swapping Nodes in a Linked List

Walk k - 1 steps from the head to get the k-th node from the front. Then use a gap pointer to get the k-th node from the end. Swap their values.

Hint: Find the Duplicate Number

Treat each index i as a node whose next is nums[i]. The duplicate value is the one two indices point to, so it is the entry of a cycle. Run Floyd from index 0.

Key takeaways#

  • Different speeds: loops and middles. Same speed with a gap: k-th from the end.
  • Inside a loop the gap shrinks by one per step, so fast always catches slow in O(n)O(n).
  • The meeting point is not the entry; reset one pointer to head and walk both one step at a time.
  • Start gap pointers from a dummy so the left one stops before the node to delete.
  • Compare nodes with is, not by value.
Revision notes · 2 min

Core idea#

Two pointers solve most forward-only list problems in O(1)O(1) space. Move them at different speeds to find a loop or the middle. Move them at the same speed with a fixed gap to find the k-th node from the end.

Key points#

  • Cycle: slow moves 1, fast moves 2. With a loop they meet. Without one, fast reaches None.
  • Inside the loop, the gap shrinks by exactly 1 per step, so they meet in O(n)O(n).
  • The meeting point is not the cycle entry. Reset one pointer to head and move both 1 step. They meet at the entry.
  • Why: let a = head to entry, b = entry to meeting point, c = loop length. Then a + b = k * c, so a steps from the meeting point land on the entry.
  • Gap: give right a k-node head start, then move both. Start left at a dummy so it stops before the target.
  • Intersection: switch heads. Each pointer walks len(A) + len(B) nodes, so both reach the shared node, or None, together.
  • An array whose values are valid indices (i -> nums[i]) behaves like a list. Cycle tricks apply.

Which middle#

Start Even length: slow ends on
slow = fast = head the second middle
slow = head, fast = head.next the first middle (use for merge sort)

Template#

slow = fast = head
while fast is not None and fast.next is not None:
    slow moves 1 step; fast moves 2 steps
    if slow is fast:                 # phase 1: cycle found
        slow = head                  # phase 2: reset one
        while slow is not fast:
            slow moves 1 step; fast moves 1 step
        return slow                  # the entry
return None                          # no cycle

Pitfalls#

  • Treating the meeting point as the loop start.
  • fast = head.next for Floyd. It breaks the a + b math, and phase 2 can loop forever. Start both at head.
  • Checking fast.next.next without fast.next. The condition is fast and fast.next.
  • Comparing values instead of nodes. Use is.

Say it in an interview#

  • "Fast gains one step per move inside the loop, so it must catch slow."
  • "To find the entry, reset one pointer to the head and walk both one step at a time."

Found a mistake? Email ask@bigonotes.com