0Pricing
DSA Interview Prep · Lesson

Two Pointers: Slow and Fast

Apply the slow-fast pointer pattern to remove duplicates in-place, move zeroes, and partition arrays around a pivot value.

Two Pointers: Slow and Fast is a free DSA Interview Prep lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the DSA Interview Prep learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Slow and Fast Pointers Explained

The slow-fast pointer pattern (also called tortoise-and-hare) uses two pointers moving at different speeds through the same sequence. Unlike opposite-ends pointers, both start at the beginning. The slow pointer advances one step at a time; the fast pointer advances two (or more). Their difference in speed creates useful invariants: the slow pointer tracks a 'valid prefix' while the fast pointer scans ahead for conditions.

# Slow pointer marks the write position;
# Fast pointer scans for next non-duplicate.

def remove_duplicates(nums):
    if not nums: return 0
    slow = 0  # next position to write a unique value
    for fast in range(1, len(nums)):
        if nums[fast] != nums[slow]:
            slow += 1
            nums[slow] = nums[fast]
    return slow + 1  # new length

nums = [1, 1, 2, 3, 3, 3, 4]
k = remove_duplicates(nums)
print(nums[:k])  # [1, 2, 3, 4]

Remove Duplicates from Sorted Array

In a sorted array, duplicates are adjacent. The slow pointer tracks the last unique value written; the fast pointer scans ahead. Whenever the fast pointer reaches a value different from nums[slow], advance slow and copy the new value. This in-place algorithm runs in O(n) time with O(1) extra space — a standard interview question testing mastery of the read-write pointer pattern.

def remove_duplicates_v2(nums):
    slow = 0
    for fast in range(len(nums)):
        if nums[fast] != nums[slow]:
            slow += 1
            nums[slow] = nums[fast]
    return slow + 1

# Allow at most 2 occurrences
def remove_duplicates_k2(nums):
    slow = 0
    for fast in range(len(nums)):
        if slow < 2 or nums[fast] != nums[slow - 2]:
            nums[slow] = nums[fast]
            slow += 1
    return slow

print(remove_duplicates_k2([1,1,1,2,2,3]))
# Result: 5, nums[:5] = [1,1,2,2,3]

Move Zeroes with Slow-Fast

Move all zeroes to the end while preserving the relative order of non-zero elements. The slow pointer marks the next position for a non-zero element. The fast pointer scans for non-zero values. When fast finds one, copy it to slow's position and advance both. After the scan, fill positions from slow to end with zeroes. O(n) time, O(1) space.

def move_zeroes(nums):
    slow = 0  # next position for a non-zero
    for fast in range(len(nums)):
        if nums[fast] != 0:
            nums[slow] = nums[fast]
            slow += 1
    # Fill rest with zeroes
    while slow < len(nums):
        nums[slow] = 0
        slow += 1

nums = [0, 1, 0, 3, 12]
move_zeroes(nums)
print(nums)  # [1, 3, 12, 0, 0]

Partition Array Around a Pivot

The partition sub-step of quick sort rearranges elements in-place so all values < pivot come before values >= pivot. The Lomuto scheme uses a slow pointer (marking the last position of a small element) and a fast pointer (scanning forward). When fast finds a small element, increment slow and swap. This runs in O(n) time with O(1) extra space.

def lomuto_partition(nums, low, high):
    pivot = nums[high]
    slow = low - 1  # last position of small element
    for fast in range(low, high):
        if nums[fast] <= pivot:
            slow += 1
            nums[slow], nums[fast] = nums[fast], nums[slow]
    # Place pivot in final position
    nums[slow+1], nums[high] = nums[high], nums[slow+1]
    return slow + 1  # pivot's final index

arr = [3, 1, 4, 1, 5, 9, 2, 6]
p = lomuto_partition(arr, 0, len(arr)-1)
print(arr)   # elements before p are <= pivot

Find Middle of a Linked List

With slow-fast pointers on a linked list, the fast pointer advances two nodes per step and the slow pointer advances one. When fast reaches the end, slow is at the middle. This O(n) one-pass approach is far cleaner than counting nodes and then walking halfway. It is used as a sub-step in merge sort for linked lists and in palindrome linked-list detection.

class Node:
    def __init__(self, val, nxt=None):
        self.val = val
        self.next = nxt

def find_middle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    return slow  # slow is at middle

# Build 1->2->3->4->5
h = Node(1, Node(2, Node(3, Node(4, Node(5)))))
mid = find_middle(h)
print(mid.val)  # 3  (middle of 5 nodes)

Cycle Detection: Floyd's Tortoise and Hare

Floyd's cycle detection places slow and fast pointers at the head of a linked list. Slow advances one node; fast advances two. If a cycle exists, the fast pointer will eventually lap the slow pointer and they will meet inside the cycle. If fast reaches None, there is no cycle. The meeting is guaranteed because fast gains one step on slow each iteration — in a cycle of length k, they meet within k steps of slow entering the cycle.

class ListNode:
    def __init__(self, val=0, nxt=None):
        self.val = val
        self.next = nxt

def has_cycle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow is fast:  # identity check (same object)
            return True
    return False

# 1->2->3->4->2 (cycle at node 2)
n1 = ListNode(1)
n2 = ListNode(2)
n3 = ListNode(3)
n4 = ListNode(4)
n1.next=n2; n2.next=n3; n3.next=n4; n4.next=n2
print(has_cycle(n1))  # True

Find Cycle Entry Point

After detecting a cycle (slow == fast), reset one pointer to head. Now advance both pointers one step at a time. They will meet at the cycle's entry point. This uses the mathematical property that the distance from head to cycle entry equals the distance from the meeting point to cycle entry (modulo cycle length). This is a beautiful mathematical result that frequently appears in hard interview problems.

def detect_cycle(head):
    slow = fast = head
    # Phase 1: detect
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow is fast:
            break
    else:
        return None  # no cycle
    # Phase 2: find entry
    slow = head
    while slow is not fast:
        slow = slow.next
        fast = fast.next
    return slow  # cycle entry node

# Using same cycled list as previous scene
print(detect_cycle(n1).val)  # 2  (cycle entry)

Slow-Fast for Happy Number

Slow-fast pointers apply beyond linked lists to any process that cycles. A 'happy number' cycles through digit-square sums — if n is not happy, the sequence eventually loops. Detect the loop with slow (one step = one digit-square) and fast (two steps). If they meet at 1, n is happy; otherwise it is trapped in a non-1 cycle. This is Floyd's algorithm applied to a virtual linked list of values.

def is_happy(n):
    def next_val(x):
        total = 0
        while x:
            x, d = divmod(x, 10)
            total += d * d
        return total

    slow = n
    fast = next_val(n)
    while fast != 1 and slow != fast:
        slow = next_val(slow)
        fast = next_val(next_val(fast))
    return fast == 1

print(is_happy(19))   # True  (1->9->...->1)
print(is_happy(2))    # False (enters a cycle)

Nth Node from End of List

Find the nth node from the end of a linked list in a single pass using two pointers. Advance the fast pointer n steps ahead. Then advance both pointers together until fast reaches the end — slow is now at the nth node from the end. To delete this node, keep a 'prev' pointer one step behind slow. This is a classic single-pass linked-list problem that avoids counting total length first.

def remove_nth_from_end(head, n):
    dummy = ListNode(0)
    dummy.next = head
    fast = slow = dummy
    # Advance fast n+1 steps
    for _ in range(n + 1):
        fast = fast.next
    # Advance together
    while fast:
        slow = slow.next
        fast = fast.next
    # slow.next is the nth from end
    slow.next = slow.next.next
    return dummy.next

# Build 1->2->3->4->5, remove 2nd from end
h2 = ListNode(1,ListNode(2,ListNode(3,ListNode(4,ListNode(5)))))
result = remove_nth_from_end(h2, 2)
# Should give 1->2->3->5

Slow-Fast in String Problems

Slow-fast thinking applies to array and string problems too. When compressing a run-length-encoded string, the slow pointer marks the write position and the fast pointer scans to the end of each run. When all characters in the run equal slow's character, advance fast; otherwise record the run and update slow. This achieves O(n) in a single pass with O(1) space.

def compress(chars):
    slow = fast = 0
    while fast < len(chars):
        char = chars[fast]
        count = 0
        # Count the run
        while fast < len(chars) and chars[fast] == char:
            fast += 1
            count += 1
        chars[slow] = char
        slow += 1
        if count > 1:
            for c in str(count):
                chars[slow] = c
                slow += 1
    return slow

chars = list('aabcccccaa')
print(compress(chars))  # 6
print(chars[:6])        # ['a','2','b','c','5','a']... wait
# Actually: ['a','2','b','c','5','a','2']

Choosing Between Slow-Fast and Opposite-Ends

Use opposite-ends pointers when the problem involves pairs summing to a target, palindrome checks, or squeezing a window from both sides. Use slow-fast pointers when you need a write pointer (remove/move elements), when processing linked-list structure (middle, cycle), or when detecting cycles in any value sequence. Both eliminate nested loops and achieve O(n) — the deciding factor is the structure of the traversal.

# Pattern matcher:
# 1. Sorted array, target sum -> OPPOSITE ENDS
# 2. Remove/filter elements in-place -> SLOW-FAST (read-write)
# 3. Linked list middle/cycle -> SLOW-FAST (1x vs 2x speed)
# 4. Detect cycle in value sequence -> SLOW-FAST (Floyd)

# Example: given sorted array, remove val in-place
def remove_sorted(nums, val):
    slow = 0
    for fast in range(len(nums)):
        if nums[fast] != val:
            nums[slow] = nums[fast]
            slow += 1
    return slow

nums = [0,1,2,2,3,0,4,2]
print(remove_sorted(nums, 2))  # 5

Quick Check

Test your understanding of Data Structures & Algorithms — Coding Interview Prep concepts from this lesson.

Lesson Recap

In this lesson you learned: the slow-fast (read-write) pattern keeps a write pointer at the next valid position while a fast pointer scans forward — the backbone of in-place remove, deduplicate, and move-zeroes, Floyd's tortoise-and-hare detects cycles in O(n) time and O(1) space by exploiting the speed difference between two pointers, and after detecting a cycle, resetting one pointer to head and advancing both at equal speed finds the cycle entry due to a provable distance equality. Next up we explore the Python string API for interviews.

Frequently asked questions

Is the “Two Pointers: Slow and Fast” lesson free?

Yes — the full text of “Two Pointers: Slow and Fast” is free to read here on the web, and the DSA Interview Prep course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the DSA Interview Prep course, upgrade to CoddyKit PRO.

What will I learn in “Two Pointers: Slow and Fast”?

Apply the slow-fast pointer pattern to remove duplicates in-place, move zeroes, and partition arrays around a pivot value. You practise DSA Interview Prep with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start DSA Interview Prep?

No prior experience is required. DSA Interview Prep on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Two Pointers: Slow and Fast” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this DSA Interview Prep lesson?

Yes. Every DSA Interview Prep lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Array Basics and In-Place Operations
  2. Prefix Sums and Running Totals
  3. Two Pointers: Opposite Ends
  4. Two Pointers: Slow and Fast
← Back to DSA Interview Prep