Cycles & the middle node
In one line
Move a slow pointer one step and a fast pointer two steps: if they meet there is a cycle, and when the fast one reaches the end the slow one is at the middle, all in O(n) time and O(1) space.
What it is
Fast and slow pointers (Floyd's tortoise and hare) walk the same list at different speeds. On a list without a cycle, the fast pointer reaches the end after about n / 2 of its moves, at which point the slow pointer has covered half the list: it is at the middle.
If the list has a cycle, the fast pointer never reaches the end. Once both pointers are inside the loop, the fast one gains one node per step, so it must land on the slow one within one lap. With a little arithmetic, the meeting point also leads you to the node where the cycle begins.
The alternative, storing every visited node in a hash set, is also O(n) time but needs O(n) memory. Floyd's method needs only two pointers.
Why interviewers ask
Linked List Cycle, Linked List Cycle II and Middle of the Linked List are very common interview questions, and the middle node is a step in palindrome, reorder and merge sort problems. Interviewers check that you can explain why the pointers must meet and why the cycle-start trick works, not just recite the code, and that you handle the even-length case for the middle.
How it works
- 1Set slow = fast = head. Loop while fast and fast.next exist: slow = slow.next, fast = fast.next.next.
- 2Middle: when the loop ends, slow is the middle. For even length (for example 4 nodes), this version returns the second middle; start fast at head.next to get the first middle instead.
- 3Cycle detection: if at any point slow is fast (the same node), there is a cycle. If fast reaches None, there is none.
- 4Why they meet: once both are in a cycle of length L, the gap between them shrinks by exactly 1 per step, so it reaches 0 in fewer than L steps. Total time is O(n).
- 5Cycle start: let a be the distance from head to the cycle start and b the distance from the start to the meeting point. Fast travelled twice as far, so 2(a + b) = a + b + kL, which gives a = kL - b. Walking a steps from the meeting point therefore lands exactly on the cycle start.
- 6So reset one pointer to head, keep the other at the meeting point, and move both one step at a time; the node where they meet is the start of the cycle.
Worked example · 1 -> 2 -> 3 -> 4 -> 5, with 5.next pointing back to 3
- The cycle is 3 -> 4 -> 5 -> 3, length 3, starting at node 3 (a = 2 steps from the head).
- Start: slow = 1, fast = 1. Step 1: slow = 2, fast = 3. Step 2: slow = 3, fast = 5. Step 3: slow = 4, fast goes 5 -> 3 -> 4, so fast = 4.
- slow and fast are both node 4: a cycle exists. The meeting point is b = 1 step past the cycle start.
- Reset p = head (node 1) and keep q = node 4. Move both one step: p = 2, q = 5. Again: p = 3, q = 3. They meet at node 3, the cycle start. Check: a = kL - b = 3 - 1 = 2.
- Middle of a list without a cycle, 1 -> 2 -> 3 -> 4 -> 5: slow and fast go (2, 3), then (3, 5); fast.next is None, so stop. slow = 3, the middle.
Picture it
Two runners on a track, one twice as fast. On a straight road the fast one finishes when the slow one is halfway. On a circular track the fast one eventually laps the slow one and they meet. If they never meet and the fast one reaches a finish line, the road had no loop.
Reference code
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def cycle_start(head):
"""Return the node where the cycle begins, or None if there is no cycle."""
slow = fast = head
while fast and fast.next:
slow, fast = slow.next, fast.next.next
if slow is fast: # they met inside the cycle
p = head
while p is not slow: # both move 1 step; they meet at the start
p, slow = p.next, slow.next
return p
return None # fast fell off the end: no cycle
def middle_node(head):
"""Middle node; for even length returns the second of the two middles."""
slow = fast = head
while fast and fast.next:
slow, fast = slow.next, fast.next.next
return slow
cycle_start first detects a meeting point, then uses the a = kL - b result to walk a fresh pointer from the head in step with the meeting pointer. middle_node is the same loop without the meeting check. Both are O(n) time and O(1) space. Use 'is' to compare node identity, not values.
Key patterns
Detect a cycle
When: Does the list loop back on itself
How: slow 1 step, fast 2 steps; meeting means a cycle, fast reaching None means no cycle.
Find where the cycle starts
When: Return the entry node of the loop
How: After they meet, move one pointer to head and step both by 1; they meet at the start.
Middle node
When: Split a list in half, palindrome check, merge sort
How: slow and fast from head; slow is the second middle for even length. Start fast at head.next for the first middle.
Implicit linked list
When: Array values point to indexes (Find the Duplicate Number), or a function applied repeatedly (Happy Number)
How: Treat i -> nums[i] or x -> f(x) as next pointers and run Floyd's algorithm on it.
Fixed gap pointers
When: k-th node from the end
How: Move fast k steps ahead first, then move both by 1 until fast reaches the end.
Real-world use
Pollard's rho factorisation
Pollard's rho algorithm for factoring integers iterates x -> x^2 + c mod n, a sequence that eventually cycles; it uses Floyd's (or Brent's) cycle detection to find the cycle with constant memory.
Cryptanalysis and random number generators
Finding hash collisions or the period of a pseudo-random generator is a cycle-finding problem on a huge implicit sequence; rho-style attacks use Floyd or Brent cycle detection because storing every value would need far too much memory.
Loop protection in operating systems
When resolving symbolic links, Linux stops after a fixed number of hops (40) and returns ELOOP rather than storing visited links: the same goal as cycle detection, solved with a cheap bound instead of a visited set.
In a project
A workflow engine lets users chain steps where each step names its next step. A misconfigured chain that loops back forever hung a worker in production. You need a fast validation check when a workflow is saved, with chains up to 100,000 steps.
- Treat each step as a node whose next pointer is its configured next step.
- Run fast and slow pointers from the first step; if fast reaches a terminal step, the chain is valid.
- If they meet, reset one pointer to the first step and advance both one step at a time to find the step where the loop begins.
- Reject the save with a clear message naming the loop's first step, and also cap runtime steps at execution as a second line of defence.
Impact: Infinite-loop workflows are caught at save time instead of hanging workers, the check uses O(1) extra memory and runs in milliseconds, and the error tells users exactly which step to fix.
Trade-offs
Use it when
- You need cycle detection or the middle with O(1) extra memory
- The sequence is implicit (x -> f(x)) and storing visited values is expensive
- Splitting a list into halves for merge sort, palindrome or reorder
Avoid it when
- You also need the full set of visited nodes for other reasons: a hash set is simpler
- Explaining Floyd's proof under time pressure is risky and memory is not a concern: a visited set is acceptable if you state the O(n) space
- You already know the length: walking n // 2 steps finds the middle directly
Cost: O(n) time, O(1) space. The hash-set alternative is O(n) time and O(n) space.
Common mistakes
- Checking fast.next.next without first checking fast and fast.next, causing a null error on even-length lists.
- Comparing node values instead of node identity; two different nodes can hold the same value.
- Checking slow == fast before moving them, which is true at the start and reports a false cycle.
- Not knowing which middle the code returns for even lengths, then splitting the list incorrectly in merge sort (infinite recursion on two nodes).
- Saying the cycle-start trick works 'because of symmetry' instead of giving the 2(a + b) = a + b + kL argument.
Interview questions
Quick revision
- slow += 1, fast += 2, loop while fast and fast.next.
- Meet means cycle; fast hits None means no cycle.
- Cycle start: reset one to head, step both by 1, meet at start.
- Proof: 2(a + b) = a + b + kL, so a = kL - b.
- Middle: slow when loop ends (second middle for even n).
- First middle: start fast at head.next.
- Compare nodes with 'is', not values.
- O(n) time, O(1) space; hash set is O(n) space.
Check yourself
1. Using slow = fast = head and 'while fast and fast.next', which node is returned as the middle of 1 -> 2 -> 3 -> 4?
2. Why must the fast and slow pointers meet if there is a cycle?
3. After the pointers meet, how do you find the start of the cycle?
4. What is the space complexity of Floyd's cycle detection?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.