Skip to content
DSA patterns 7 min read

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
}

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;
}

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 != null before fast.next.next. Checking only fast != null crashes 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+1 values in [1, n] is fast/slow in disguise — treat next = 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).

Last updated June 25, 2026
Was this helpful?