Fast and Slow Pointers: Solving the 'Linked List Cycle' Problem

Hero image for Fast and Slow Pointers: Solving the 'Linked List Cycle' Problem. Image by Hassaan Tahir.
Hero image for 'Fast and Slow Pointers: Solving the 'Linked List Cycle' Problem.' Image by Hassaan Tahir.

Linked List Cycle is one of those problems that becomes much easier once we stop trying to inspect the list from above and instead think about what repeated movement would look like from inside it. We are asked a very small question:

  • does following next eventually end?
  • or do we get trapped in a loop?

The obvious answer is to remember every node we have seen already. That works. The more interesting answer is Floyd's cycle detection algorithm, usually called the fastandslow pointer approach.

That second answer is worth learning because it turns a memory problem into a movement problem.


The Straightforward Solution: Remember Visited Nodes

If nodes are objects, then object identity gives us a very direct check.

type ListNode = {  val: number;  next: ListNode | null;};export const hasCycle = (head: ListNode | null): boolean => {  const seen = new Set<ListNode>();  let current = head;  while (current) {    if (seen.has(current)) {      return true;    }    seen.add(current);    current = current.next;  }  return false;};

This is a perfectly respectable first pass. It is easy to read and hard to get wrong.

The downside is that it uses extra space proportional to the number of nodes visited. For this problem, we can do better.


Why the Fast‑And‑Slow Pattern Works at All

Now imagine two pointers walking through the list:

  • slow moves one step at a time
  • fast moves two steps at a time

If the list ends, fast eventually hits null, so there is no cycle.

If the list loops, then once both pointers are inside the cycle, the faster pointer keeps gaining on the slower one. It is a bit like running around a circular track. If one runner is consistently quicker, they eventually catch up.

That is the whole idea. The proof is not mystical. It is just relative speed on a loop.


A Practical TypeScript Solution

type ListNode = {  val: number;  next: ListNode | null;};export const hasCycle = (head: ListNode | null): boolean => {  let slow = head;  let fast = head;  while (fast && fast.next) {    slow = slow?.next ?? null;    fast = fast.next.next;    if (slow === fast) {      return true;    }  }  return false;};

Why the Meeting Point Proves a Cycle Exists

This is the part people often accept too quickly without really trusting it.

If there is no cycle:

  • the list has an end
  • the fast pointer runs out of list first

So if the pointers ever meet after moving, it cannot be in a normal terminating list.

If there is a cycle:

  • both pointers eventually enter the loop
  • inside the loop, the fast pointer gains one node per round relative to the slow pointer

That gain keeps accumulating until the fast pointer lands on the same node as the slow pointer.

We do not need to know where they meet. We only need to know that meeting is inevitable in a looped path.


Comparing the Two Solutions Properly

The visitedset version is better in one narrow sense:

  • it is easier to derive

That matters. A good first answer is still a good answer.

The fastandslow version is better for the actual problem constraints:

  • it uses O(1) extra space
  • it teaches a reusable linkedlist pattern
  • it does not depend on storing every node reference we pass through

So if I were explaining the problem from scratch, I would mention the set first because it shows the shape of the question clearly. If I were choosing the final answer, I would always pick fast and slow pointers.


Why This is Better than Trying to Count Steps

Another instinct people sometimes have is to think there should be some maximum reasonable number of steps before we conclude the list must be cycling. That approach is unreliable because we are not given a safe limit in advance. Long acyclic lists exist too.

What makes Floyd's algorithm strong is that it does not guess. It uses a structural fact:

  • in a finite loop, two pointers moving at different speeds must eventually collide

That is much sturdier than any stepcount heuristic.


A Small Trace Makes It Easier to Trust

Suppose the list looks like this:

3 -> 2 -> 0 -> -4     ^         |     |_________|

Now walk the pointers:

  • start both at 3
  • after one move, slow is at 2, fast is at 0
  • after two moves, slow is at 0, fast is back at 2
  • after three moves, both land on 4

The exact positions are not the important part. The important part is that the gap between them keeps changing inside a closed loop until it becomes zero.


Common Mistakes

Comparing Node Values Instead of Node References

Two different nodes can hold the same value. Cycle detection is about identity, not about matching val.

Forgetting the fast.next check

The loop condition needs to protect the doublestep:

while (fast && fast.next)

Assuming a Meeting Can Happen in a Normal List

With both pointers starting together and then moving at different speeds, a genuine meeting after movement implies a loop.


The Wider Lesson Behind the Pattern

This problem is really a reminder that linkedlist questions often reward movement patterns more than extra containers.

We already saw linkedlist merging in the site's other LeetCode pieces. This one adds a different kind of insight:

  • sometimes the useful information is not stored in the nodes
  • sometimes it comes from how we traverse them

That is why the fastandslow technique shows up again in problems such as finding the middle node, checking whether a list is a palindrome, or locating the start of a cycle after one has been detected.

The Part Worth Keeping

  • A Set of visited nodes is a sound first answer.
  • Floyd's algorithm is the better final answer because it gets the same result in constant extra space.
  • The reason it works is simple once the cycle is treated like a looped track: the faster pointer must eventually catch the slower one.

Linked List Cycle is a good pattern problem because the elegant answer is not about clever syntax. It is about trusting the movement.


Categories:

  1. Algorithms
  2. Data Structures
  3. Development
  4. Guides
  5. JavaScript
  6. LeetCode
  7. TypeScript