How to Detect a Cycle in a Linked List Using Floyd’s Tortoise and Hare Algorithm
Floyd’s Tortoise and Hare algorithm detects a cycle in a linked list by moving two pointers at different speeds—one step at a time for the slow pointer and two steps for the fast pointer—returning true if they meet inside a loop or false if the fast pointer reaches the end.
Detecting a cycle in a linked list is a fundamental interview problem solved optimally with constant space using the two-pointer technique known as Floyd’s cycle detection. The kdn251/interviews repository provides battle-tested Java implementations of this algorithm across LeetCode and company-specific modules. This guide walks through the exact implementation found in leetcode/two-pointers/LinkedListCycle.java and explains why this approach guarantees O(n) time complexity with O(1) auxiliary space.
Algorithm Overview
The Tortoise and Hare strategy relies on two pointers traversing the list at different velocities:
- Slow pointer (Tortoise): Advances one node per iteration (
slow = slow.next) - Fast pointer (Hare): Advances two nodes per iteration (
fast = fast.next.next)
If the list is acyclic, the fast pointer reaches the end (null) first, terminating the search. If the list contains a cycle, the fast pointer eventually laps the slow pointer from behind, causing them to reference the same node. This meeting point proves the existence of a loop.
Implementation Details
The repository implements this logic in the hasCycle(ListNode head) method. You can find identical implementations across multiple interview preparation tracks, including:
leetcode/two-pointers/LinkedListCycle.javacompany/microsoft/LinkedListCycle.javacompany/amazon/LinkedListCycle.javacompany/bloomberg/LinkedListCycle.java
The Core Method
According to the source code in leetcode/two-pointers/LinkedListCycle.java, the implementation handles edge cases first, then enters a traversal loop:
public boolean hasCycle(ListNode head) {
// Edge case: empty list or single node without a next pointer → no cycle
if (head == null || head.next == null) {
return false;
}
// Initialize pointers
ListNode slow = head; // moves 1 step
ListNode fast = head.next; // moves 2 steps (starts one step ahead)
// Traverse until fast reaches the end or meets slow
while (fast != null && fast.next != null && fast != slow) {
slow = slow.next; // one step
fast = fast.next.next; // two steps
}
// If fast and slow met, a cycle exists
return fast == slow;
}
Key implementation details:
- Initialization: The fast pointer starts at
head.nextrather thanheadto ensure the while-loop conditionfast != slowworks correctly from the first iteration. - Null checks: The loop condition verifies both
fast != nullandfast.next != nullto preventNullPointerExceptionwhen advancing the fast pointer. - Termination: The method returns
trueonly whenfast == slow, confirming the pointers converged inside a cycle.
Practical Code Examples
Example 1: Handcrafted Cycle Detection
// Build nodes
ListNode a = new ListNode(1);
ListNode b = new ListNode(2);
ListNode c = new ListNode(3);
a.next = b;
b.next = c;
c.next = a; // creates a cycle (c → a)
// Detect cycle
Solution sol = new Solution();
System.out.println(sol.hasCycle(a)); // prints: true
Example 2: Dynamic List with Injected Cycle
// Assume we have a utility that builds a linked list from an array
int[] values = {4, 5, 6, 7};
ListNode head = ListBuilder.fromArray(values); // no cycle initially
Solution sol = new Solution();
System.out.println(sol.hasCycle(head)); // prints: false
// Introduce a cycle for testing
head.next.next.next.next = head.next; // tail points to second node
System.out.println(sol.hasCycle(head)); // prints: true
Example 3: LeetCode-Style Runner
public class Main {
public static void main(String[] args) {
// Example list: 1 → 2 → 3 → 4 → 2 (cycle starts at node with value 2)
ListNode n1 = new ListNode(1);
ListNode n2 = new ListNode(2);
ListNode n3 = new ListNode(3);
ListNode n4 = new ListNode(4);
n1.next = n2;
n2.next = n3;
n3.next = n4;
n4.next = n2; // creates cycle
Solution solution = new Solution();
System.out.println(solution.hasCycle(n1)); // true
}
}
Why This Algorithm Works
In an acyclic list, the fast pointer moves twice as fast toward the null terminator. Since it starts ahead and moves two steps per iteration, it will always reach null before or at the same time the slow pointer reaches the final node.
In a cyclic list, once both pointers enter the loop, the distance between them changes by exactly one node per iteration (the fast pointer gains one node on the slow pointer each step). Because the loop length is finite, the fast pointer will eventually close the gap and occupy the same node as the slow pointer, confirming the cycle. This convergence is mathematically guaranteed regardless of the cycle's length or entry point.
Complexity Analysis
- Time Complexity: O(n) where n is the number of nodes. In the worst case, the algorithm traverses the list once before finding the cycle or reaching the end.
- Space Complexity: O(1) auxiliary space. The solution uses only two pointer variables (
slowandfast) regardless of input size, unlike hash-based approaches that require O(n) space to store visited nodes.
Summary
- Floyd’s Tortoise and Hare algorithm uses two pointers moving at 1x and 2x speeds to detect cycles without extra data structures.
- The
hasCycle()method inleetcode/two-pointers/LinkedListCycle.javainitializesfastathead.nextand iterates until pointers meet orfastreaches null. - This approach guarantees linear time complexity while maintaining constant space overhead, making it optimal for embedded or memory-constrained environments.
- The kdn251/interviews repository duplicates this implementation across company-specific directories (Microsoft, Amazon, Bloomberg), demonstrating its universal applicability in technical interviews.
Frequently Asked Questions
Why does Floyd’s algorithm use two pointers moving at different speeds?
The different speeds ensure that if a cycle exists, the fast pointer will eventually lap the slow pointer from behind. If both pointers moved at the same speed, they would maintain a constant distance inside a cycle and never meet unless they started at the exact same node. The 2:1 speed differential guarantees the distance between them decreases by one node per iteration inside the loop, forcing convergence.
Can I use a HashSet to detect a cycle instead of Floyd’s algorithm?
Yes, but it requires O(n) space complexity. You would traverse the list while storing node references in a HashSet<ListNode>, returning true if set.add(node) returns false (indicating the node was visited before). While simpler to implement, this approach consumes memory proportional to the list length, whereas Floyd’s algorithm detects a cycle in a linked list using only two pointers with O(1) space.
What happens if both pointers start at the head instead of offsetting the fast pointer?
If both slow and fast start at head and you check for equality after moving both pointers, the loop condition must be structured carefully to avoid immediate false positives (they start equal). The implementation in leetcode/two-pointers/LinkedListCycle.java avoids this by initializing fast = head.next, ensuring the first comparison happens after at least one movement. Both initialization patterns work if the loop logic is adjusted accordingly.
How do I find the start of the cycle once detected?
Once the tortoise and hare meet, reset one pointer to the head while keeping the other at the meeting point. Then advance both pointers one step at a time. The node where they meet again is the start of the cycle. This works because the distance from the head to the cycle start equals the distance from the meeting point to the cycle start when measured along the loop.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →