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

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
nexteventually 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 fast‑and‑slow 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:
slowmoves one step at a timefastmoves 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 visited‑set version is better in one narrow sense:
- it is easier to derive
That matters. A good first answer is still a good answer.
The fast‑and‑slow version is better for the actual problem constraints:
- it uses
O(1)extra space - it teaches a reusable linked‑list 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 step‑count 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,
slowis at2,fastis at0 - after two moves,
slowis at0,fastis back at2 - 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 double‑step:
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 linked‑list questions often reward movement patterns more than extra containers.
We already saw linked‑list 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 fast‑and‑slow 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
Setof 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.
Related Articles

Building Multi‑Tenant Applications with Next.js. 
Object.assign() in JavaScript: Merging and Shallow Copies. Object.assign()in JavaScript: Merging and Shallow Copies
Longest Substring Without Repeating Characters in JavaScript. Longest Substring Without Repeating Characters in JavaScript

The Quirks of z‑index. The Quirks of
z‑index
Creating Custom Vue Directives for Enhanced Functionality. Creating Custom Vue Directives for Enhanced Functionality

Practical Use Cases for JavaScript Set and Map. Practical Use Cases for JavaScript
SetandMap
Sorting Objects in JavaScript. Sorting Objects in JavaScript

LocalStorage in JavaScript. localStoragein JavaScript
Using Regex to Replace Numbers in a String. Using Regex to Replace Numbers in a String

Rethinking Carousels: Going Around in Circles. Rethinking Carousels: Going Around in Circles

Using the filter() Method in JavaScript. Using the
filter()Method in JavaScript
Understanding Arrow Functions in JavaScript. Understanding Arrow Functions in JavaScript