DEV Community

Cover image for Two Pointers in Practice: Opposite Direction vs Fast and Slow Pointers
DEVANSHU PATIL
DEVANSHU PATIL

Posted on AI-assisted

Two Pointers in Practice: Opposite Direction vs Fast and Slow Pointers

Two Pointers in Practice: Opposite Direction vs Fast and Slow Pointers

Introduction

The Two Pointers pattern is a fundamental algorithmic strategy used to traverse linear data structures—such as arrays, strings, or linked lists—using two reference pointers simultaneously. By intelligently adjusting these pointers based on runtime conditions, we can often reduce time complexity from $O(n^2)$ (brute force) down to $O(n)$, while keeping space complexity at an optimal $O(1)$.

While the term "two pointers" is broad, interview problems and real-world system optimization tasks generally rely on two primary variations:

  1. Opposite Direction Pointers (Converging Pointers)
  2. Fast and Slow Pointers (Tortoise and Hare)

In this technical guide, we will analyze both variants, explore their underlying mechanics, and implement production-ready solutions for four classic algorithmic problems.

Variant 1: Opposite Direction (Converging Pointers)

Mechanics

The Opposite Direction pattern initializes two pointers at opposing ends of a sequence: one at the beginning (left = 0) and one at the end (right = n - 1). The pointers move toward each other, meeting in the middle. This approach is primarily used on sorted arrays or strings where symmetry or comparative summation guides the traversal.

Complexity Profile

  • Time Complexity: $O(n)$ because each element is visited at most once.
  • Space Complexity: $O(1)$ as no auxiliary data structures are required.

Problem 1: Two Sum II (Sorted Input Array)

Problem Statement

Given a 1-indexed array of integers numbers that is already sorted in non-decreasing order, find two numbers such that they add up to a specific target number. Return the indices of the two numbers.

Python Implementation

from typing import List

def two_sum(numbers: List[int], target: int) -> List[int]:
    left = 0
    right = len(numbers) - 1

    while left < right:
        current_sum = numbers[left] + numbers[right]

        if current_sum == target:
            # Return 1-indexed positions
            return [left + 1, right + 1]
        elif current_sum < target:
            # We need a larger sum, move the left pointer rightward
            left += 1
        else:
            # We need a smaller sum, move the right pointer leftward
            right -= 1

    return []
Enter fullscreen mode Exit fullscreen mode

Problem 2: Valid Palindrome

Problem Statement

A phrase is a palindrome if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward. Alphanumeric characters include letters and numbers.

Python Implementation

def is_palindrome(s: str) -> bool:
    left = 0
    right = len(s) - 1

    while left < right:
        # Skip non-alphanumeric characters from the left
        while left < right and not s[left].isalnum():
            left += 1
        # Skip non-alphanumeric characters from the right
        while left < right and not s[right].isalnum():
            right -= 1

        # Compare characters case-insensitively
        if s[left].lower() != s[right].lower():
            return False

        left += 1
        right -= 1

    return True
Enter fullscreen mode Exit fullscreen mode

Variant 2: Fast and Slow Pointers (Tortoise and Hare)

Mechanics

The Fast and Slow pointer pattern uses two pointers moving through the structure at different speeds (usually 1 step vs. 2 steps per iteration). This strategy is predominantly applied to linked lists or array-based sequences where we need to deal with cyclical dependencies, find specific structural properties, or locate midpoints.

Complexity Profile

  • Time Complexity: $O(n)$ linear traversal.
  • Space Complexity: $O(1)$ auxiliary space.

Problem 3: Linked List Cycle Detection (Floyd's Tortoise and Hare)

Problem Statement

Given head, the head of a linked list, determine if the linked list has a cycle in it. There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next pointer.

Python Implementation

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

def has_cycle(head: ListNode) -> bool:
    slow = head
    fast = head

    while fast is not None and fast.next is not None:
        slow = slow.next          # Moves 1 step
        fast = fast.next.next     # Moves 2 steps

        if slow == fast:
            return True           # Cycle detected

    return False                  # Reached end of list, no cycle
Enter fullscreen mode Exit fullscreen mode

Engineering Insight: If a cycle exists, the fast pointer enters the cycle first and laps the slow pointer. Because the relative speed is 1 node per iteration, they are guaranteed to meet inside the loop without missing each other.

Problem 4: Middle of the Linked List

Problem Statement

Given the head of a singly linked list, return the middle node of the linked list. If there are two middle nodes, return the second middle node.

Python Implementation

def middle_node(head: ListNode) -> ListNode:
    slow = head
    fast = head

    # When fast reaches the end, slow will be at the middle
    while fast is not None and fast.next is not None:
        slow = slow.next
        fast = fast.next.next

    return slow
Enter fullscreen mode Exit fullscreen mode

Why This Works

As the fast pointer traverses the list at double speed ($2x$), the slow pointer travels at single speed ($1x$). When fast reaches the terminal node (None for odd-length lists) or the last node (fast.next is None for even-length lists), slow has traversed exactly half the distance, resting precisely at the median node.

Comparative Summary

Feature Opposite Direction Pointers Fast and Slow Pointers
Primary Data Structure Sorted Arrays, Strings Linked Lists, Sequences
Initial Positions Start and End (0, n-1) Both at Head / Start (0, 0)
Traversal Speed Converging inward ($1$ step each) Variable ($1$x vs $2$x speed)
Primary Use Cases Pair matching, palindromes, partitioning Cycle detection, midpoint finding

Conclusion

Mastering the Two Pointers pattern requires recognizing structural invariants in data. When an array is sorted, think Opposite Direction to cancel out nested loops. When traversing linked sequences or evaluating cyclic references without hash sets, deploy Fast and Slow Pointers. Applying these paradigms cleanly leads to optimal $O(n)$ time and $O(1)$ space solutions.

Top comments (0)