Intuition
Race a slow pointer (one step) against a fast pointer (two steps). With no cycle, fast runs off the end. With a cycle, fast can never escape and steadily laps slow until they collide.
Approach
The hash-set answer costs memory
You can store every node you visit in a set and declare a cycle the moment you revisit one. It's simple and O(n) time, but it's O(n) space. The elegant solution gets the same answer with two pointers and no extra memory — Floyd's tortoise and hare.
A fast runner must lap a slow one inside a loop
Run slow one step at a time and fast two steps at a time. If the list ends, fast (or fast.next) hits null and there's clearly no cycle. But if there is a cycle, fast can never escape it; once both pointers are inside the loop, fast gains exactly one node on slow every step, so the gap shrinks to zero and they must collide. A collision is therefore proof of a cycle.
Step, then compare
While fast and fast.next exist, advance slow by 1 and fast by 2, then check whether they point to the same node (identity, not value). If so, return true; if fast runs off the end, return false. O(n) time, O(1) space.
Solution & live demo
Edge cases
fast or fast.next becomes null and the loop exits with false.
The pointers still meet inside the loop; identity (is) compares nodes, not values.
The loop condition is false immediately, returning false.