{"id":4527,"date":"2024-10-21T10:36:51","date_gmt":"2024-10-21T10:36:51","guid":{"rendered":"https:\/\/algocademy.com\/blog\/fast-and-slow-pointers-a-powerful-technique-for-solving-linked-list-problems\/"},"modified":"2026-10-15T10:34:49","modified_gmt":"2026-10-15T10:34:49","slug":"fast-and-slow-pointers-a-powerful-technique-for-solving-linked-list-problems","status":"publish","type":"post","link":"https:\/\/algocademy.com\/blog\/fast-and-slow-pointers-a-powerful-technique-for-solving-linked-list-problems\/","title":{"rendered":"Fast and Slow Pointers: Solve Linked List Problems in One Pass"},"content":{"rendered":"<p>Fast and slow pointers means walking a linked list with two references at different speeds, usually one node per step for <code>slow<\/code> and two for <code>fast<\/code>. Because the gap between them changes in a predictable way, you can detect a cycle, find the middle, or find the nth node from the end in a single pass with O(1) extra memory. This is also called Floyd&#8217;s tortoise and hare algorithm.<\/p>\n<p>All the code below is Python and was run on the sample lists shown. The node class and a small helper to build lists are in the first block.<\/p>\n<h2>The setup<\/h2>\n<pre><code class=\"language-python\">class ListNode:\n    def __init__(self, val=0, next=None):\n        self.val = val\n        self.next = next\n\ndef build(values, cycle_to=None):\n    \"\"\"Build a list from values; if cycle_to is an index, link the tail back to that node.\"\"\"\n    dummy = tail = ListNode()\n    nodes = []\n    for v in values:\n        tail.next = ListNode(v)\n        tail = tail.next\n        nodes.append(tail)\n    if cycle_to is not None:\n        tail.next = nodes[cycle_to]\n    return dummy.next\n<\/code><\/pre>\n<p>Every problem below uses the same loop shape:<\/p>\n<pre><code class=\"language-python\">slow = fast = head\nwhile fast and fast.next:\n    slow = slow.next\n    fast = fast.next.next\n<\/code><\/pre>\n<p>The condition <code>fast and fast.next<\/code> is the part people get wrong. You need both checks, in this order, because <code>fast.next.next<\/code> crashes if <code>fast<\/code> is <code>None<\/code> or <code>fast.next<\/code> is <code>None<\/code>. The loop ends when <code>fast<\/code> falls off the end, and at that moment <code>slow<\/code> is at the halfway point.<\/p>\n<h2>1. Detect a cycle<\/h2>\n<p>If the list has no cycle, <code>fast<\/code> reaches <code>None<\/code> and the loop ends. If there is a cycle, both pointers end up inside it, and <code>fast<\/code> gains exactly one node on <code>slow<\/code> every step. A gap that closes by one each step cannot be jumped over, so they must land on the same node.<\/p>\n<pre><code class=\"language-python\">def has_cycle(head):\n    slow = fast = head\n    while fast and fast.next:\n        slow = slow.next\n        fast = fast.next.next\n        if slow is fast:\n            return True\n    return False\n\nprint(has_cycle(build([1, 2, 3, 4, 5])))              # False\nprint(has_cycle(build([1, 2, 3, 4, 5], cycle_to=2)))  # True\n<\/code><\/pre>\n<p>Compare nodes with <code>is<\/code>, not <code>==<\/code> on values: two different nodes can hold the same value. The alternative is a set of visited nodes, which is simpler to write but uses O(n) memory. Fast and slow pointers use O(1).<\/p>\n<h2>2. Find where the cycle starts<\/h2>\n<p>This is the classic follow-up (&#8220;Linked List Cycle II&#8221;). Once the pointers meet, reset one of them to the head and move both one step at a time. They meet again at the first node of the cycle.<\/p>\n<pre><code class=\"language-python\">def cycle_start(head):\n    slow = fast = head\n    while fast and fast.next:\n        slow = slow.next\n        fast = fast.next.next\n        if slow is fast:\n            slow = head\n            while slow is not fast:\n                slow = slow.next\n                fast = fast.next\n            return slow\n    return None\n\nstart = cycle_start(build([1, 2, 3, 4, 5], cycle_to=2))\nprint(start.val)                       # 3\nprint(cycle_start(build([1, 2, 3])))   # None\n<\/code><\/pre>\n<p>Why it works: let <em>a<\/em> be the number of nodes before the cycle, <em>c<\/em> the cycle length, and <em>k<\/em> how far into the cycle the meeting point is. When they meet, <code>slow<\/code> has taken <em>a + k<\/em> steps and <code>fast<\/code> has taken twice that. Both are at the same spot, so the extra distance <em>a + k<\/em> is a whole number of laps: <em>a + k = m\u00b7c<\/em>. Rearranged, <em>a = m\u00b7c &#8211; k<\/em>, which says that walking <em>a<\/em> more steps from the meeting point lands on the cycle start. A pointer from the head also needs <em>a<\/em> steps to get there, so they arrive together.<\/p>\n<h2>3. Find the middle node<\/h2>\n<p>When <code>fast<\/code> has moved to the end, <code>slow<\/code> has moved half as far.<\/p>\n<pre><code class=\"language-python\">def middle(head):\n    slow = fast = head\n    while fast and fast.next:\n        slow = slow.next\n        fast = fast.next.next\n    return slow\n\nprint(middle(build([1, 2, 3, 4, 5])).val)     # 3\nprint(middle(build([1, 2, 3, 4, 5, 6])).val)  # 4\n<\/code><\/pre>\n<p>For an even-length list this returns the second of the two middle nodes. If a problem wants the first one (you need it to split a list in two, for instance), change the loop condition to <code>while fast.next and fast.next.next<\/code>. Check which one your problem asks for and test with both an odd and an even list.<\/p>\n<h2>4. Remove or find the nth node from the end<\/h2>\n<p>Here the pointers move at the same speed, but you give <code>fast<\/code> a head start of <em>n<\/em> nodes. When <code>fast<\/code> reaches the last node, <code>slow<\/code> is just before the node you want. Starting both at a dummy node before the head handles the case where the head itself is removed.<\/p>\n<pre><code class=\"language-python\">def remove_nth_from_end(head, n):\n    dummy = ListNode(0, head)\n    fast = slow = dummy\n    for _ in range(n):\n        fast = fast.next\n    while fast.next:\n        fast = fast.next\n        slow = slow.next\n    slow.next = slow.next.next\n    return dummy.next\n\ndef to_list(head):\n    out = []\n    while head:\n        out.append(head.val)\n        head = head.next\n    return out\n\nprint(to_list(remove_nth_from_end(build([1, 2, 3, 4, 5]), 2)))  # [1, 2, 3, 5]\nprint(to_list(remove_nth_from_end(build([1, 2, 3, 4, 5]), 5)))  # [2, 3, 4, 5]\n<\/code><\/pre>\n<p>This code assumes <em>n<\/em> is between 1 and the list length, as most versions of the problem guarantee. If it might not be, check while advancing <code>fast<\/code>. You can practice this exact problem in the <a href=\"https:\/\/algocademy.com\/link\/?lang=py&amp;problem=remove-nth-node-from-end-of-list&amp;solution=1\">Remove Nth Node From End Of List lesson<\/a>.<\/p>\n<h2>5. Check for a palindrome<\/h2>\n<p>A list cannot be indexed from both ends, so the trick is to find the middle, reverse the second half in place, and compare the halves.<\/p>\n<pre><code class=\"language-python\">def is_palindrome(head):\n    if not head:\n        return True\n    slow = fast = head\n    while fast.next and fast.next.next:\n        slow = slow.next\n        fast = fast.next.next\n    # reverse everything after slow\n    prev, cur = None, slow.next\n    while cur:\n        cur.next, prev, cur = prev, cur, cur.next\n    left, right = head, prev\n    while right:\n        if left.val != right.val:\n            return False\n        left, right = left.next, right.next\n    return True\n\nprint(is_palindrome(build([1, 2, 3, 2, 1])))  # True\nprint(is_palindrome(build([1, 2, 2, 1])))     # True\nprint(is_palindrome(build([1, 2, 3])))        # False\n<\/code><\/pre>\n<p>This runs in O(n) time and O(1) space, but it modifies the list. In an interview, say so, and mention that you can reverse the second half again before returning if the caller needs the original list. Reversing a list is its own pattern; see <a href=\"https:\/\/algocademy.com\/blog\/in-place-reversal-of-a-linked-list-a-comprehensive-guide\/\">in-place reversal of a linked list<\/a> or the <a href=\"https:\/\/algocademy.com\/link\/?lang=py&amp;problem=reverse-linked-list&amp;solution=1\">Reverse Linked List lesson<\/a>.<\/p>\n<h2>It is not only for linked lists<\/h2>\n<p>Any sequence that is produced by repeatedly applying a function can have a cycle, and Floyd&#8217;s method finds it without storing what it has seen. The standard example is &#8220;Happy Number&#8221;: repeatedly replace a number with the sum of the squares of its digits; it either reaches 1 or loops forever.<\/p>\n<pre><code class=\"language-python\">def is_happy(n):\n    def step(x):\n        return sum(int(d) ** 2 for d in str(x))\n    slow, fast = n, step(n)\n    while fast != 1 and slow != fast:\n        slow = step(slow)\n        fast = step(step(fast))\n    return fast == 1\n\nprint(is_happy(19))  # True\nprint(is_happy(2))   # False\n<\/code><\/pre>\n<p>&#8220;Find the Duplicate Number&#8221; (an array of n+1 values in the range 1 to n, treated as next-pointers) uses the same idea, including the cycle-start step.<\/p>\n<h2>Complexity<\/h2>\n<table>\n<thead>\n<tr>\n<th>Problem<\/th>\n<th>Time<\/th>\n<th>Extra space<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Detect cycle<\/td>\n<td>O(n)<\/td>\n<td>O(1)<\/td>\n<\/tr>\n<tr>\n<td>Find cycle start<\/td>\n<td>O(n)<\/td>\n<td>O(1)<\/td>\n<\/tr>\n<tr>\n<td>Middle node<\/td>\n<td>O(n)<\/td>\n<td>O(1)<\/td>\n<\/tr>\n<tr>\n<td>Nth from end<\/td>\n<td>O(n)<\/td>\n<td>O(1)<\/td>\n<\/tr>\n<tr>\n<td>Palindrome check<\/td>\n<td>O(n)<\/td>\n<td>O(1)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The cycle case is O(n) because once <code>slow<\/code> enters the cycle, <code>fast<\/code> catches it in at most one lap of the cycle.<\/p>\n<h2>Common mistakes<\/h2>\n<ul>\n<li><strong>Wrong loop condition.<\/strong> <code>while fast.next.next<\/code> crashes on empty and one-node lists. Use <code>while fast and fast.next<\/code>.<\/li>\n<li><strong>Comparing values instead of nodes.<\/strong> A list like 1 -&gt; 2 -&gt; 1 has repeated values and no cycle.<\/li>\n<li><strong>Starting <code>fast<\/code> and <code>slow<\/code> at different nodes<\/strong> and then forgetting that it shifts which middle you get, or breaking the cycle-start proof. If you start <code>fast<\/code> at <code>head.next<\/code>, the &#8220;reset and walk together&#8221; step no longer lands on the cycle start.<\/li>\n<li><strong>Skipping the dummy node<\/strong> when the operation may remove the head.<\/li>\n<li><strong>Not testing the edge cases:<\/strong> empty list, one node, two nodes, and a cycle that points back to the head.<\/li>\n<\/ul>\n<h2>When to reach for it<\/h2>\n<p>Think of fast and slow pointers when a linked list problem asks for something relative to the end or the middle, when you must detect a loop without extra memory, or when a sequence is generated by a function and might repeat. If you can afford O(n) memory, a hash set is often easier to get right, so say that you considered it. For arrays, the related two-pointer and sliding window patterns cover similar ground; our <a href=\"https:\/\/algocademy.com\/blog\/mastering-the-sliding-window-technique-a-comprehensive-guide\/\">sliding window guide<\/a> is a good next read, and the <a href=\"https:\/\/algocademy.com\/link\/?lang=py&amp;problem=linked-lists&amp;solution=0\">Linked Lists lesson<\/a> covers the basics if the node class above is unfamiliar.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>How the fast and slow pointer (tortoise and hare) technique finds cycles, middles, palindromes and nth-from-end nodes, with tested Python code.<\/p>\n","protected":false},"author":1,"featured_media":4526,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[23],"tags":[],"class_list":["post-4527","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-problem-solving"],"_links":{"self":[{"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/posts\/4527"}],"collection":[{"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/comments?post=4527"}],"version-history":[{"count":1,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/posts\/4527\/revisions"}],"predecessor-version":[{"id":8706,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/posts\/4527\/revisions\/8706"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/media\/4526"}],"wp:attachment":[{"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/media?parent=4527"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/categories?post=4527"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/tags?post=4527"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}