The Fast & Slow Pointers Pattern: Template & When to Use It
The fast & slow pointers pattern (a.k.a. Floyd’s tortoise and hare) moves two pointers through a sequence at different speeds — the slow one step at a time, the fast two — so their relative positions reveal structural facts like cycles, the middle element, or the nth node from the end, all in O(n) time and O(1) space. This page recaps the signal and the template.
For the linked-list topic treatment, see fast & slow pointers.
The signal: when to recognize it
This pattern lives mostly in linked lists and in sequences defined by “next = f(current).” Look for:
- “Does this linked list have a cycle?” / “Where does the cycle begin?”
- “Find the middle node in one pass.”
- “Find the nth node from the end in one pass.”
- A sequence of numbers where each value points to the next index (e.g. find the duplicate number framed as cycle detection).
- “Is this number happy?” and similar iterate-until-repeat problems.
For beginners: Imagine two runners on a circular track. A faster runner will lap a slower one — if they ever meet, there’s a loop. On a straight track (no cycle), the fast runner just reaches the end first. That single idea powers the whole pattern.
Why it works
When the fast pointer moves twice as fast, on a cycle the gap between fast and slow shrinks by one each step, so they are guaranteed to meet inside the loop. With no cycle, fast simply hits the end (null). For the middle, when fast reaches the end, slow has traveled exactly half — landing on the middle.
Template: cycle detection (and finding the middle)
This detects whether a singly linked list has a cycle. The same fast/slow walk, stopped when fast hits the end, leaves slow on the middle node.
struct ListNode { int val; ListNode* next; };
// Floyd's cycle detection: true if the list contains a cycle.
bool hasCycle(ListNode* head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast != nullptr && fast->next != nullptr) {
slow = slow->next; // 1 step
fast = fast->next->next; // 2 steps
if (slow == fast) return true; // they met: cycle
}
return false; // fast reached the end: no cycle
}
class ListNode { int val; ListNode next; }
// Floyd's cycle detection: true if the list contains a cycle.
boolean hasCycle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next; // 1 step
fast = fast.next.next; // 2 steps
if (slow == fast) return true; // they met: cycle
}
return false; // fast reached the end: no cycle
}
// node shape: { val, next }
// Floyd's cycle detection: true if the list contains a cycle.
function hasCycle(head) {
let slow = head, fast = head;
while (fast !== null && fast.next !== null) {
slow = slow.next; // 1 step
fast = fast.next.next; // 2 steps
if (slow === fast) return true; // they met: cycle
}
return false; // fast reached the end: no cycle
}
# node shape: node.val, node.next
# Floyd's cycle detection: true if the list contains a cycle.
def has_cycle(head):
slow = fast = head
while fast is not None and fast.next is not None:
slow = slow.next # 1 step
fast = fast.next.next # 2 steps
if slow is fast:
return True # they met: cycle
return False # fast reached the end: no cycle
Template: find the middle node
A direct application — when fast runs off the end, slow sits on the middle (the second middle for an even-length list).
struct ListNode { int val; ListNode* next; };
// Middle node in one pass (second middle if length is even).
ListNode* middleNode(ListNode* head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast != nullptr && fast->next != nullptr) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
class ListNode { int val; ListNode next; }
// Middle node in one pass (second middle if length is even).
ListNode middleNode(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
// node shape: { val, next }
// Middle node in one pass (second middle if length is even).
function middleNode(head) {
let slow = head, fast = head;
while (fast !== null && fast.next !== null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
# node shape: node.val, node.next
# Middle node in one pass (second middle if length is even).
def middle_node(head):
slow = fast = head
while fast is not None and fast.next is not None:
slow = slow.next
fast = fast.next.next
return slow
Complexity
O(n) time — the slow pointer traverses at most the whole list once — and O(1) space, the key advantage over the obvious “store every node in a hash set” cycle check, which costs O(n) memory.
Common pitfalls
- Null dereference: always guard
fast != null && fast.next != nullbeforefast.next.next. Checking onlyfast != nullcrashes on the last node. - Finding the cycle start: after a meeting, reset one pointer to the head and advance both one step at a time; they meet at the cycle’s entry. (A meeting alone only proves a cycle exists.)
- Even vs odd length: decide whether you want the first or second middle and adjust the loop condition accordingly.
Going deeper: Find the duplicate number in an array of
n+1values in[1, n]is fast/slow in disguise — treatnext = a[current]and detect the cycle.
Practice
Drill it in the linked list exercises. It pairs naturally with reversing a linked list for palindrome-list checks (find middle, reverse half, compare).