When you hear a problem that talks about a cycle, a middle, or a kth element from the end, the fast‑slow pointer pattern is often the cleanest solution. The idea is simple: run two pointers through the data structure at different speeds—commonly one step for the "slow" pointer and two steps for the "fast" pointer. Because the fast pointer moves twice as quickly, it either reaches the end sooner, meets the slow pointer inside a cycle, or creates a fixed offset that lets you compute positions without extra memory.

Why the Pattern Works

The mathematics behind the pattern is straightforward. If the fast pointer moves 2 steps for every 1 step of the slow pointer, after k iterations the fast pointer has traveled 2k nodes while the slow pointer has traveled k. In a cycle of length c, the distance between them reduces by 1 each iteration, guaranteeing a meeting point after at most c steps. For non‑cyclic structures, the fast pointer simply runs out of nodes first, giving you a natural termination condition.

Signals That Call for Fast‑Slow Pointers

Problem cueTypical goal
"detect a cycle" or "loop"Find if a linked list repeats
"find the middle"Return the center node/value
"kth from the end"Locate a node k steps behind the tail
"remove the nth node from end"Delete a node using O(1) space
"check palindrome without extra memory"Compare two halves of a list

If you see any of these phrases, ask yourself whether you need a constant‑space traversal that keeps track of relative positions. That’s a strong hint that the fast‑slow technique belongs in your toolbox.

A Worked Example: Detecting a Cycle in a Singly‑Linked List

class ListNode:
    def __init__(self, val=0, nxt=None):
        self.val = val
        self.next = nxt

def has_cycle(head: ListNode) -> bool:
    """Return True if the linked list contains a cycle.
    Uses O(1) extra space and O(n) time.
    """
    slow = fast = head
    while fast and fast.next:          # fast must have two steps ahead
        slow = slow.next               # one step
        fast = fast.next.next          # two steps
        if slow is fast:               # pointers met -> cycle
            return True
    return False                      # fast reached end -> no cycle

Explanation: Both pointers start at the head. In each loop iteration, slow moves one node, fast moves two. If a cycle exists, the fast pointer eventually laps the slow one, causing slow is fast. If there is no cycle, fast hits a None reference and the loop exits.

Complexity

  • Time: O(n) – each node is visited at most twice.
  • Space: O(1) – only two pointer variables are used.

Common Pitfalls

  • Missing null checks: fast must have a next node before you access fast.next.next; otherwise you’ll get an AttributeError.
  • Off‑by‑one errors: Starting both pointers at head.next can miss a cycle that begins at the head.
  • Using equality (==) instead of identity (is): In Python, == checks value equality, which may be true for distinct nodes with the same value. Use is to compare object identity.

Five Representative Practice Problems

1. Find the Middle of a Linked List

Goal: Return the node at the middle; if the list has even length, return the second middle. Hint: Advance fast two steps and slow one step until fast reaches the end.

2. Remove the Nth Node From End of a List

Goal: Delete the node that is n positions from the tail in one pass. Hint: Move fast n steps ahead, then advance both pointers together until fast hits the end; slow will point to the predecessor of the target.

3. Detect a Cycle and Return Its Start Node

Goal: Not only detect a cycle but also locate the node where the cycle begins. Hint: After the first meeting, reset one pointer to head and move both one step at a time; they meet at the cycle start.

4. Check if a Linked List Is a Palindrome (O(1) Space)

Goal: Determine whether the list reads the same forward and backward. Hint: Use fast‑slow to find the middle, reverse the second half in place, then compare halves.

5. Find the Length of the Longest Subarray With Equal Number of 0s and 1s

Goal: Return the maximum length of a contiguous subarray containing the same count of zeros and ones. Hint: Transform 0 → -1, then use a fast‑slow style sliding window where the fast pointer expands the window and the slow pointer contracts when the sum deviates from zero.

Each problem can be solved with the same core idea—maintaining two pointers at different speeds—to achieve linear time and constant extra memory.

When Not to Use Fast‑Slow Pointers

  • The problem explicitly requires storing additional state (e.g., frequencies) that cannot be derived from relative positions.
  • The data structure is not sequential (e.g., a graph where edges branch arbitrarily); a breadth‑first search may be more appropriate.
  • The constraints allow O(n) extra space and the solution becomes clearer with a hash map or auxiliary array.

How to Practice This

  1. Pick a problem from the list above, write the solution on paper first, then code it in Python without looking at references.
  2. Run edge cases: empty list, single node, even vs. odd lengths, and cycles that start at the head.
  3. Explain the solution aloud as if you were in an interview. Tools like Call Assistant can record your answer and keep the narrative focused, helping you refine the story and stay on track.

FAQ

  • Q: Can fast‑slow pointers be used on arrays? A: Yes. For example, to find a duplicate in a sorted array where each element appears twice except one, you can advance a fast index by two and a slow index by one, comparing values each step.

  • Q: What if the list is doubly linked? A: The pattern still works, but you often have more options (e.g., moving from both ends). Fast‑slow remains a clean O(1)‑space solution for cycle detection.

  • Q: How do I avoid off‑by‑one bugs? A: Write the loop condition as while fast and fast.next: and move pointers inside the loop exactly as described. Test with both odd and even lengths.

  • Q: Is there a version that uses three pointers? A: Some problems (e.g., partitioning a list into three parts) benefit from a third pointer, but the core fast‑slow idea stays the same—maintain a constant‑space relationship between pointers.

Frequently asked questions

Can fast‑slow pointers be used on arrays?

Yes. For example, to find a duplicate in a sorted array where each element appears twice except one, you can advance a fast index by two and a slow index by one, comparing values each step.

What if the list is doubly linked?

The pattern still works, but you often have more options (e.g., moving from both ends). Fast‑slow remains a clean O(1)-space solution for cycle detection.

How do I avoid off‑by‑one bugs?

Write the loop condition as `while fast and fast.next:` and move pointers inside the loop exactly as described. Test with both odd and even lengths.

Is there a version that uses three pointers?

Some problems (e.g., partitioning a list into three parts) benefit from a third pointer, but the core fast‑slow idea stays the same—maintain a constant‑space relationship between pointers.

#coding pattern#fast and slow pointers#interview prep#linked list#algorithm