Fast & Slow Pointers — Floyd's Cycle Detection
Two pointers moving at different speeds through a linked list will meet inside a cycle if one exists — and never meet if there is none. This one idea detects cycles, finds their entry point, and with a small twist, solves Happy Number and Duplicate in Array problems too.
Table of contents
- Before we start
- Picture this first (no code yet)
- First, the slow way (so you feel the pain)
- The turning point
- The one idea to remember
- Part 1 — Cycle detection
- Part 2 — Find the cycle entry point (LC #142)
- The math (plain words, no symbols)
- Application: Happy Number (LC #202)
- Where to spot this pattern
- Common traps
- Complexity
- Say it like a pro (interview one-liner)
- Check yourself
- Practice problems
Before we start
You have seen two pointers move in the same direction (Chapter 1.2) and in opposite directions (Chapter 1.1). This chapter introduces a third configuration: two pointers on the same structure, moving at different speeds — one step at a time (slow) and two steps at a time (fast). By the end you will be able to:
- Detect whether a linked list has a cycle in O(n) time, O(1) space.
- Find the exact node where the cycle begins — with a two-phase proof.
- Apply the same idea to the Happy Number problem (cycle detection on a number sequence).
Picture this first (no code yet)
A real-life story
Two runners set off on a cross-country course. One runs at normal pace; the other runs twice as fast. If the course is a straight path, the fast runner reaches the end first and waits — they never meet again. If the course loops back on itself (a cycle), the fast runner will eventually lap the slow runner — they will meet somewhere on the loop, guaranteed.
That is Floyd's algorithm. Slow pointer moves one node per step. Fast pointer moves two nodes per step. If there is a cycle, fast laps slow and they collide. If the list ends (fast hits null), there is no cycle.
First, the slow way (so you feel the pain)
The obvious approach: keep a visited set. As you traverse, check if the current node is already in the set.
def hasCycle_set(head):
seen = set()
node = head
while node:
if id(node) in seen:
return True # revisited a node — cycle!
seen.add(id(node))
node = node.next
return False
This works, but uses O(n) space — one entry per node. For a list of 10 million nodes, that is 10 million entries in memory. Floyd's algorithm detects the cycle using O(1) space — just two pointer variables, regardless of list length.
For n = 10 000 000: visited-set uses ~400 MB of memory. Floyd uses 8 bytes (two pointers). Same time complexity — O(n) — radically different space.
The turning point
Pause & think
The fast pointer moves at 2× speed. If both start at the head and there is a cycle, why must they eventually meet? Could the fast pointer "skip over" the slow pointer and never land on the same node?
Think of it like a clock: if one hand moves twice as fast as the other, do they ever point to the same time?
Why they must meet
Once both pointers are inside the cycle (which happens within at most cycle_length steps), think of their relative position. Each step, fast gains exactly 1 position on slow (moves 2, slow moves 1 → gap shrinks by 1). Starting from any gap of size k, after exactly k more steps, the gap is 0 — they are on the same node.
The fast pointer cannot "skip over" slow because the gap shrinks by exactly 1 each step — it goes k, k−1, k−2, …, 1, 0 (meet). It passes through 0 before going negative, which in a cycle would wrap back around. So a meeting is guaranteed.
The one idea to remember
The entire pattern in one sentence
Move slow one step and fast two steps per iteration — if they ever point to the same node, a cycle exists; if fast reaches null, the list is acyclic; O(n) time, O(1) space.
Part 1 — Cycle detection
def hasCycle(head):
slow = fast = head
while fast and fast.next: # fast needs two steps — check both
slow = slow.next # one step
fast = fast.next.next # two steps
if slow is fast:
return True # met inside cycle
return False # fast hit None — no cycle
Frame-by-frame: 1 → 2 → 3 → 4 → 2 (cycle at node 2)
Initial: slow=1, fast=1
Step 1: slow=2, fast=3
Step 2: slow=3, fast=2 (fast looped: 3→4→2)
Step 3: slow=4, fast=4 (slow: 3→4, fast: 2→3→4... wait let me re-trace)
List: 1 → 2 → 3 → 4 → (back to 2)
Step 1: slow = node2, fast = node3
Step 2: slow = node3, fast = node2 (fast: node3→node4→node2)
Step 3: slow = node4, fast = node4 (fast: node2→node3→node4)
slow is fast at node4 → cycle detected ✅
Frame-by-frame: 1 → 2 → 3 (no cycle)
slow=1, fast=1
Step 1: slow=2, fast=3
Step 2: fast=fast.next.next = node3.next.next = None.next → fast.next is None → while condition fails
Return False ✅
Part 2 — Find the cycle entry point (LC #142)
This is the harder variant: not just "does a cycle exist?" but "at which node does the cycle begin?"
The answer comes from a beautiful mathematical property of where slow and fast meet.
The math (plain words, no symbols)
Let:
F= distance from head to the cycle entry pointC= length of the cyclea= distance from cycle entry to the meeting point (measured forward around the cycle)
When they meet:
- Slow has traveled:
F + asteps - Fast has traveled:
F + a + Csteps (it has gone around the cycle one full extra time) - Fast travels twice as far:
F + a + C = 2(F + a)
Solving: C - a = F
This says: the distance from the meeting point back to the cycle entry (going forward around the cycle) equals the distance from the head to the cycle entry.
So: reset one pointer to the head, keep the other at the meeting point, and move both one step at a time. They will meet exactly at the cycle entry.
def detectCycle(head):
slow = fast = head
# Phase 1: find meeting point
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow is fast:
break
else:
return None # no cycle
# Phase 2: find cycle entry
slow = head # reset one pointer to head
while slow is not fast: # keep other at meeting point
slow = slow.next
fast = fast.next # both move ONE step now
return slow # they meet at the cycle entry
Frame-by-frame: 3 → 1 → 0 → -4 → (back to 1)
List: 3 → 1 → 0 → -4 → (cycle to 1)
F = 1 (head to node 1)
C = 3 (cycle: 1 → 0 → -4 → 1)
Phase 1 (2-speed):
slow=3, fast=3
Step 1: slow=1, fast=0
Step 2: slow=0, fast=1 (fast: 0→-4→1)
Step 3: slow=-4, fast=-4 (fast: 1→0→-4)
Meeting point: node -4
Phase 2 (1-speed, slow resets to head=3):
slow=3, fast=-4
Step 1: slow=1, fast=1 ← both at node 1
Return node 1 ✅ (the cycle entry)
Verify: F=1, meeting distance from entry a=2 (entry→0→-4), C-a=3-2=1=F ✅
Application: Happy Number (LC #202)
A "happy number" is defined by repeatedly replacing n with the sum of the squares of its digits. If this process eventually reaches 1, the number is happy. If it cycles endlessly without reaching 1, it is not.
This is cycle detection on a number sequence, not a linked list. Same algorithm:
def isHappy(n: int) -> bool:
def next_val(x):
total = 0
while x:
x, digit = divmod(x, 10)
total += digit ** 2
return total
slow = n
fast = next_val(n)
while fast != 1 and slow != fast:
slow = next_val(slow)
fast = next_val(next_val(fast))
return fast == 1 # if they met at 1, it's happy; if they met elsewhere, it's a cycle
The key insight: unhappy numbers always end up in the cycle 4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4. The fast/slow pointers detect this cycle in O(log n) steps per next_val call.
Where to spot this pattern
Trigger words:
- "detect cycle in linked list"
- "find where the cycle begins"
- "happy number" / "any repeated state in a sequence"
- "find duplicate number" (Duplicate Number with space constraint — Floyd on array indices)
- "find the missing/duplicate" — when you can model it as a function
f(i) = array[i]
5 disguises:
- Linked List Cycle (LC #141): direct application — hasCycle.
- Linked List Cycle II (LC #142): two-phase — find entry point.
- Happy Number (LC #202): Floyd on digit-sum sequence.
- Find the Duplicate Number (LC #287): treat array as linked list
f(i) = nums[i]. Duplicate ↔ cycle entry. O(n) time, O(1) space — no sorting, no set. - Palindrome Linked List (LC #234): uses fast/slow to find middle first (Chapter 4.2), then reverses second half.
Common traps
Watch out for these
- Checking
fast.nextas well asfast.fast.next.nextrequires thatfast.nextis not None. The loop condition must bewhile fast and fast.next:— both. Missingfast.nextcauses a NullPointerException on the two-step. - Using
==instead ofisfor node comparison. In Python,slow == fastmight compare values (if__eq__is defined);slow is fastcompares object identity — whether they are the same node. Always useisfor linked list node comparison. - Forgetting Phase 2 for cycle entry. After the meeting, reset exactly one pointer to
headand move both one step. Don't reset both — one stays at the meeting point. - Starting both at
headfor Phase 1 but not moving before first comparison. If both start atheadandheadis in the cycle, the first checkif slow is fastwould immediately trigger. Start withslow = head.next, fast = head.next.next— or, equivalently, move before checking, which thewhileloop structure already ensures.
Complexity
| Variant | Time | Space |
|---|---|---|
| Cycle detection (hasCycle) | O(n) | O(1) |
| Cycle entry (detectCycle) | O(n) | O(1) |
| Happy Number | O(log n) per step, O(log n) steps | O(1) |
| Visited-set approach | O(n) | O(n) |
Floyd always wins on space. Time is equivalent.
Say it like a pro (interview one-liner)
"I'll use Floyd's fast-and-slow pointer algorithm — slow moves one step, fast moves two. If they meet, a cycle exists. To find the cycle entry, I reset slow to the head and move both one step at a time — they meet at the entry because the head-to-entry distance equals the meeting-point-to-entry distance around the cycle. O(n) time, O(1) space."
Remember this forever
Floyd's Fast & Slow — 2 phases
# Phase 1: detect & find meeting point
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow is fast: break # cycle found
else: return None # no cycle
# Phase 2: find cycle entry
slow = head # reset one pointer
while slow is not fast:
slow = slow.next
fast = fast.next # both ONE step now
return slow # cycle entry node
Math: meeting point satisfies F = C − a, so head→entry = meeting→entry (forward around cycle).
Trap: while fast and fast.next (both!). Use is not == for node comparison.
Check yourself
Why can't the fast pointer "skip over" the slow pointer and miss the meeting?
Consider the relative gap between fast and slow inside the cycle. Each step, slow moves 1 position forward and fast moves 2 — so fast gains exactly 1 position on slow per step. The gap decreases by 1 each iteration: k, k−1, k−2, …, 1, 0. It hits exactly 0 before going negative (which would wrap around and bring the gap back to a positive number, repeating the countdown). So the gap inevitably reaches 0 — they must meet. The fast pointer cannot overshoot by more than 0, because it closes the gap by exactly 1 each step.
Prove in plain words why resetting one pointer to head and moving both at speed 1 leads to the cycle entry.
At the meeting point inside the cycle, slow has traveled F + a steps (F to entry, then a into the cycle). From the meeting point, going forward around the cycle, there are C − a steps back to the cycle entry. We showed C − a = F. So: the meeting point is exactly F steps away from the cycle entry (going forward around the cycle), and the head is exactly F steps away from the cycle entry (going forward in the list). Move one pointer from the head and one from the meeting point, both at speed 1, both traveling F steps — they arrive at the cycle entry simultaneously.
In "Find the Duplicate Number" (LC #287, array of n+1 integers in range 1 to n), how does Floyd's algorithm apply?
Model the array as a linked list where node i has a "next" pointer to nums[i]. Since there's a duplicate value d, two indices point to the same "next" value — creating a cycle. Index 0 is the "head" (it's never pointed to by any nums[i] since values are 1 to n, so 0 is always the entry). Run Floyd's Phase 1 to find the meeting point, then Phase 2 (reset slow to 0, move both at speed 1) to find the cycle entry — which is the duplicate number. O(n) time, O(1) space, no modification to the array.
Practice problems
| Problem | Difficulty | What to notice | Link |
|---|---|---|---|
| Linked List Cycle | Easy | Phase 1 only; use is not == | LC #141 |
| Linked List Cycle II | Medium | Both phases; reset one pointer to head | LC #142 |
| Happy Number | Easy | Floyd on digit-sum sequence | LC #202 |
| Find the Duplicate Number | Medium | Floyd on array-as-linked-list; O(1) space | LC #287 |
Next up: Find Middle of Linked List — the simplest fast/slow application: when fast reaches the end, slow is exactly at the middle.