The fast and slow pointers pattern—also known as the "tortoise and hare" algorithm—is one of the most elegant solutions in computer science.
It solves problems that seem to require O(n) space with just O(1) space. It detects cycles in linked lists with mathematical certainty. And it finds middle elements without counting.
But here's what makes it powerful: it's not just a trick—it's a fundamental technique with rigorous mathematical backing and dozens of applications.
This comprehensive guide will teach you the complete fast/slow pointers pattern, when to use it, how it works mathematically, and advanced techniques that go beyond basic cycle detection.
TL;DR
Use fast/slow pointers when:
- Working with linked lists
- Detecting cycles
- Finding middle elements
- Need O(1) space (can't use hash set)
- Problem involves different speeds of traversal
Template:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True # Cycle detected
return FalseTime: O(n), Space: O(1)
What Is Fast and Slow Pointers?
The Core Concept
Two pointers traverse a data structure at different speeds:
- Slow pointer: Moves 1 step at a time
- Fast pointer: Moves 2 steps at a time
Visual (Linked List):
Step 0: S,F -> 1 -> 2 -> 3 -> 4 -> 5 -> None
Step 1: 1 -> S -> 2 -> F -> 3 -> 4 -> 5 -> None
Step 2: 1 -> 2 -> S -> 3 -> 4 -> F -> 5 -> None
Step 3: 1 -> 2 -> 3 -> S -> 4 -> 5 -> F=NoneWhy It Works
For cycle detection: If there's a cycle, the fast pointer will eventually "lap" the slow pointer, like runners on a circular track.
For finding middle: When fast reaches the end, slow is at the middle.
Mathematical proof: See cycle detection proof.
The Universal Template
Python Template
def fast_slow_template(head):
"""
Template for fast/slow pointers on linked lists.
Args:
head: Head of linked list
Returns:
Result based on problem requirements
"""
# Edge case: empty or single node
if not head or not head.next:
return None # or appropriate default
# Initialize both pointers at head
slow = head
fast = head
# Move at different speeds
# CRITICAL: Check both fast and fast.next
while fast and fast.next:
slow = slow.next # Move 1 step
fast = fast.next.next # Move 2 steps
# Check condition (e.g., cycle detection)
if slow == fast:
return True # or appropriate result
# Fast reached end (no cycle)
return FalseJavaScript Template
function fastSlowTemplate(head) {
if (!head || !head.next) return null;
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
return true;
}
}
return false;
}Java Template
public boolean fastSlowTemplate(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;
fast = fast.next.next;
if (slow == fast) {
return true;
}
}
return false;
}Pattern 1: Detect Cycle in Linked List
Problem
Determine if a linked list has a cycle.
Solution
def hasCycle(head: Optional[ListNode]) -> bool:
"""
LeetCode #141: Linked List Cycle
Time: O(n)
Space: O(1)
"""
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True # Cycle detected
return False # No cycleWhy It Works
Intuition: If there's a cycle, both pointers will eventually enter it. Once inside, the fast pointer gains 1 position per iteration. Eventually, the gap becomes 0 and they meet.
Mathematical proof:
- Let cycle length = C
- When slow enters cycle, fast is somewhere in the cycle
- Distance between them decreases by 1 each iteration
- After at most C iterations, distance = 0 (they meet)
See detailed proof: Cycle detection proof
Example Trace
List: 3 -> 2 -> 0 -> -4 -> (back to 2)
Step 0: slow=3, fast=3
Step 1: slow=2, fast=0
Step 2: slow=0, fast=2
Step 3: slow=-4, fast=-4 ← MEET! Cycle detectedPattern 2: Find Cycle Start
Problem
If a cycle exists, find the node where it begins.
Solution
def detectCycle(head: Optional[ListNode]) -> Optional[ListNode]:
"""
LeetCode #142: Linked List Cycle II
Time: O(n)
Space: O(1)
"""
# Phase 1: Detect cycle
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
break # Cycle detected
else:
return None # No cycle
# Phase 2: Find cycle start
# Reset slow to head, move both at same speed
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow # This is the cycle startWhy It Works
Mathematical insight:
Let:
- L = distance from head to cycle start
- C = cycle length
- k = distance from cycle start to meeting point
When they meet:
- Slow traveled: L + k
- Fast traveled: L + k + nC (n complete cycles)
Since fast travels twice as fast:
2(L + k) = L + k + nC
L + k = nC
L = nC - kThis means: distance from head to cycle start = distance from meeting point to cycle start (going around the cycle).
So if we reset slow to head and move both at the same speed, they'll meet at the cycle start!
Pattern 3: Find Middle of Linked List
Problem
Find the middle node of a linked list.
Solution
def middleNode(head: Optional[ListNode]) -> Optional[ListNode]:
"""
LeetCode #876: Middle of the Linked List
Time: O(n)
Space: O(1)
"""
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow # Slow is at middleWhy It Works
Intuition: When fast reaches the end (moving 2 steps), slow has moved half as far (1 step), so it's at the middle.
Proof:
- Fast moves 2x as fast as slow
- When fast reaches end after n/2 iterations
- Slow has moved n/2 steps from start
- Slow is at position n/2 (the middle)
For odd length: Returns the middle node
For even length: Returns the second middle node
Example
List: 1 -> 2 -> 3 -> 4 -> 5
Step 0: slow=1, fast=1
Step 1: slow=2, fast=3
Step 2: slow=3, fast=5
Step 3: fast.next=None, stop
Return slow=3 (middle)Pattern 4: Remove Nth Node From End
Problem
Remove the nth node from the end of a linked list.
Solution
def removeNthFromEnd(head: Optional[ListNode], n: int) -> Optional[ListNode]:
"""
LeetCode #19: Remove Nth Node From End of List
Time: O(n)
Space: O(1)
"""
# Use dummy node to handle edge cases
dummy = ListNode(0)
dummy.next = head
slow = fast = dummy
# Move fast n+1 steps ahead
for _ in range(n + 1):
fast = fast.next
# Move both until fast reaches end
while fast:
slow = slow.next
fast = fast.next
# Remove the node
slow.next = slow.next.next
return dummy.nextWhy It Works
Technique: Maintain a gap of n nodes between fast and slow.
When fast reaches the end, slow is n nodes from the end (the node before the one to remove).
Dummy node trick: Handles the case where we need to remove the head.
Pattern 5: Palindrome Linked List
Problem
Check if a linked list is a palindrome.
Solution
def isPalindrome(head: Optional[ListNode]) -> bool:
"""
LeetCode #234: Palindrome Linked List
Time: O(n)
Space: O(1)
"""
if not head or not head.next:
return True
# Step 1: Find middle using fast/slow
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# Step 2: Reverse second half
prev = None
while slow:
next_node = slow.next
slow.next = prev
prev = slow
slow = next_node
# Step 3: Compare first half and reversed second half
left, right = head, prev
while right: # Only need to check right (shorter half)
if left.val != right.val:
return False
left = left.next
right = right.next
return TrueWhy It Works
Three-phase approach:
- Find middle with fast/slow
- Reverse second half
- Compare first half with reversed second half
Space optimization: Instead of copying to an array (O(n) space), we modify the list in-place (O(1) space).
Fast/Slow on Arrays
Pattern: Happy Number
def isHappy(n: int) -> bool:
"""
LeetCode #202: Happy Number
Treat number sequence as a linked list.
Use fast/slow to detect cycle.
Time: O(log n)
Space: O(1)
"""
def get_next(num):
total = 0
while num > 0:
digit = num % 10
total += digit * digit
num //= 10
return total
slow = n
fast = get_next(n)
while fast != 1 and slow != fast:
slow = get_next(slow)
fast = get_next(get_next(fast))
return fast == 1Why It Works
Key insight: The sequence of numbers forms an implicit linked list. If it doesn't reach 1, it must cycle.
Fast/slow detects the cycle without storing all seen numbers (O(1) space vs O(n) with hash set).
Advanced Techniques
Technique 1: Finding Cycle Length
def findCycleLength(head):
"""
Find the length of the cycle if it exists.
"""
# Detect cycle
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
break
else:
return 0 # No cycle
# Count cycle length
length = 1
current = slow.next
while current != slow:
length += 1
current = current.next
return lengthTechnique 2: Different Speed Ratios
def customSpeed(head):
"""
Fast moves 3 steps, slow moves 1 step.
Note: This can miss cycles in certain cases.
The 2:1 ratio is optimal.
"""
slow = fast = head
while fast and fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next.next # 3 steps
if slow == fast:
return True
return FalseWarning: Speeds other than 2:1 can miss cycles. Stick with the standard 2:1 ratio.
Common Mistakes
Mistake 1: Not Checking fast.next
# WRONG
while fast:
fast = fast.next.next # Crashes if fast.next is None!Fix: Always check fast and fast.next
Mistake 2: Wrong Initialization
# WRONG: Fast starts ahead
slow = head
fast = head.next # Crashes if head is None!Fix: Both start at head, or check if not head or not head.next
Mistake 3: Forgetting Edge Cases
# WRONG: Doesn't handle empty list
slow = fast = head
while fast and fast.next:
# ... (crashes if head is None)Fix: Check if not head: return None first
See comprehensive guide: Linked list pitfalls
When to Use Fast/Slow
✅ Use When:
- Linked list problems
- Cycle detection needed
- Finding middle without counting
- O(1) space required
- Implicit linked list (like Happy Number)
❌ Don't Use When:
- Arrays where you can use indices
- Need to track all elements (use hash set)
- Multiple pointers at different positions (use array)
- Tree problems (use different traversal)
Practice Problems
Master fast/slow with these problems:
Beginner:
- Linked List Cycle (#141) - basic detection
- Middle of Linked List (#876) - finding middle
- Happy Number (#202) - implicit linked list
Intermediate:
4. Linked List Cycle II (#142) - find cycle start
5. Remove Nth From End (#19) - maintain gap
6. Palindrome Linked List (#234) - multi-step
Advanced:
7. Intersection of Two Linked Lists (#160) - two lists
8. Reorder List (#143) - find middle + reverse
9. Find Duplicate Number (#287) - array as linked list
Complexity Analysis
Time Complexity: O(n)
- Cycle detection: O(n) - at most 2n steps
- Find middle: O(n/2) = O(n)
- Find cycle start: O(n) + O(n) = O(n)
Space Complexity: O(1)
- Only two pointer variables
- No additional data structures
Comparison with hash set:
- Hash set: O(n) time, O(n) space
- Fast/slow: O(n) time, O(1) space ✓
FAQ
Q: Why does the fast pointer move 2 steps, not 3 or more?
A: Moving 2 steps is optimal. It guarantees detection within C iterations (cycle length). Moving 3+ steps might cause them to miss each other in small cycles.
Q: Can fast and slow start at different positions?
A: Yes, but both starting at head is the standard and simplest approach.
Q: What if the list has only one node?
A: The condition while fast and fast.next handles this—the loop doesn't run.
Q: How do I know if a problem needs fast/slow?
A: Look for keywords: "linked list," "cycle," "middle," or "O(1) space." See pattern recognition.
Conclusion
The fast and slow pointers pattern is a fundamental technique with elegant mathematical properties.
Key principles:
- Different speeds (2:1 ratio is optimal)
- Cycle detection with mathematical certainty
- O(1) space vs hash set's O(n)
- Multiple applications beyond just cycles
The template:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return FalseWhen to use:
- Linked lists + cycles
- Finding middle
- O(1) space requirement
- Implicit linked lists
Master this pattern, and you'll solve linked list problems with confidence and elegance. For more patterns, see the complete two pointers guide and opposite direction template.
Next time you see a linked list problem, think: fast and slow pointers.
Want to Practice LeetCode Smarter?
LeetCopilot is a free browser extension that enhances your LeetCode practice with AI-powered hints, personalized study notes, and realistic mock interviews — all designed to accelerate your coding interview preparation.
Also compatible with Edge, Brave, and Opera
