Two-Pointer Technique: Complete Reference

Type: Software Reference Confidence: 0.95 Sources: 7 Verified: 2026-02-24 Freshness: 2026-02-24

TL;DR

Constraints

Quick Reference

PatternPointer InitMovement RuleTimeSpaceClassic Problems
Opposite ends (converging)left=0, right=n-1Move the pointer that improves the objectiveO(n)O(1)Two Sum II, Container With Most Water, Trapping Rain Water, Valid Palindrome
Same direction (read/write)slow=0, fast=0Fast scans all elements; slow marks write positionO(n)O(1)Remove Duplicates from Sorted Array, Remove Element, Move Zeroes
Fast/slow (cycle detection)slow=head, fast=headSlow moves 1 step, fast moves 2 stepsO(n)O(1)Linked List Cycle, Find the Duplicate Number, Happy Number
Fast/slow (midpoint)slow=head, fast=headSlow moves 1, fast moves 2; stop when fast reaches endO(n)O(1)Middle of the Linked List, Palindrome Linked List
Partitioninglo=0, hi=n-1Swap elements that belong on the other sideO(n)O(1)Sort Colors (Dutch National Flag), Partition Array
Merge (two arrays)i=0, j=0Advance the pointer with the smaller elementO(n+m)O(n+m)Merge Sorted Array, Intersection of Two Arrays
Expand from centerleft=center, right=centerExpand outward while condition holdsO(n²) totalO(1)Longest Palindromic Substring
Three pointerslo=0, mid=0, hi=n-1Partition into three regionsO(n)O(1)Sort Colors, Dutch National Flag

Decision Tree

START
|-- Is the input a linked list?
|   |-- YES: Need cycle detection?
|   |   |-- YES --> Fast/slow pointers (Floyd's algorithm)
|   |   |-- NO: Need the middle node?
|   |       |-- YES --> Fast/slow (stop when fast reaches end)
|   |       |-- NO --> Same-direction pointers
|   |-- NO (array or string) |
|-- Is the input sorted (or can you sort it)?
|   |-- YES: Looking for a pair that satisfies a condition?
|   |   |-- YES --> Opposite-ends (converging) pointers
|   |   |-- NO: Need to remove/deduplicate in-place?
|   |       |-- YES --> Same-direction read/write pointers
|   |       |-- NO --> Consider binary search instead
|   |-- NO (unsorted):
|       |-- Can you afford O(n log n) sorting first?
|       |   |-- YES --> Sort, then use converging pointers
|       |   |-- NO --> Use a hash map/set instead (O(n) time, O(n) space)
|-- Need to partition into regions (e.g., <pivot, ==pivot, >pivot)?
|   |-- YES --> Three-pointer / Dutch National Flag
|-- Need longest/shortest substring with a constraint?
|   |-- YES --> Sliding window (see related unit)
|-- DEFAULT --> Brute force or hash map

Step-by-Step Guide

1. Identify the pointer pattern

Determine which variant fits your problem. If you need to find a pair in a sorted array, use converging pointers. If you need to detect a cycle, use fast/slow. If you need to remove elements in-place, use read/write pointers. [src1]

Verify: Can you express the loop invariant? e.g., "Everything to the left of slow is processed and valid."

2. Initialize pointers correctly

For converging: left = 0, right = len(arr) - 1. For same-direction: slow = 0, fast = 0 (or fast = 1). For linked lists: slow = head, fast = head. [src2]

Verify: Both pointers point to valid positions in the data structure before the loop starts.

3. Define the loop condition

Converging: while left < right. Same-direction: while fast < len(arr). Linked list: while fast is not None and fast.next is not None. [src5]

Verify: The loop terminates in all cases. Converging pointers always reduce the gap by at least 1 per iteration.

4. Implement the movement logic

Inside the loop, decide which pointer to advance based on the problem condition. For converging pointers on two-sum: if arr[left] + arr[right] < target, increment left; if greater, decrement right; if equal, return the pair. [src1]

Verify: Each iteration advances at least one pointer, guaranteeing O(n) termination.

5. Handle edge cases

Empty input, single element, all duplicates, already-at-target. For linked lists: null head, single node, cycle that includes the head. [src3]

Verify: Test with arrays of length 0, 1, 2 and linked lists with 0, 1, 2 nodes.

Code Examples

Python: Two Sum on Sorted Array (Converging Pointers)

# Input:  sorted array of integers, integer target
# Output: tuple of 1-indexed positions (i, j) where arr[i]+arr[j]==target

def two_sum_sorted(numbers: list[int], target: int) -> tuple[int, int]:
    left, right = 0, len(numbers) - 1
    while left < right:
        current = numbers[left] + numbers[right]
        if current == target:
            return (left + 1, right + 1)  # 1-indexed
        elif current < target:
            left += 1    # need a larger sum
        else:
            right -= 1   # need a smaller sum
    raise ValueError("No solution found")

Python: Container With Most Water (Converging Pointers)

# Input:  list of non-negative integers representing heights
# Output: maximum water area between any two lines

def max_area(height: list[int]) -> int:
    left, right = 0, len(height) - 1
    best = 0
    while left < right:
        width = right - left
        h = min(height[left], height[right])
        best = max(best, width * h)
        if height[left] < height[right]:
            left += 1    # move the shorter line inward
        else:
            right -= 1
    return best

JavaScript: Remove Duplicates In-Place (Read/Write Pointers)

// Input:  sorted array of integers (nums)
// Output: length of array with duplicates removed; nums mutated in-place

function removeDuplicates(nums) {
  if (nums.length === 0) return 0;
  let slow = 0;                      // write pointer
  for (let fast = 1; fast < nums.length; fast++) {
    if (nums[fast] !== nums[slow]) { // found a new unique element
      slow++;
      nums[slow] = nums[fast];       // write it at the next position
    }
  }
  return slow + 1;                   // length of unique portion
}

Python: Linked List Cycle Detection (Floyd's Algorithm)

# Input:  head of a singly linked list (may contain a cycle)
# Output: True if cycle exists, False otherwise

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

def has_cycle(head: ListNode | None) -> bool:
    slow = fast = head
    while fast is not None and fast.next is not None:
        slow = slow.next          # 1 step
        fast = fast.next.next     # 2 steps
        if slow is fast:          # pointers met inside the cycle
            return True
    return False                  # fast reached end; no cycle

Java: Valid Palindrome (Converging Pointers with Filtering)

// Input:  string s with mixed characters
// Output: true if s is a palindrome considering only alphanumeric chars

public boolean isPalindrome(String s) {
    int left = 0, right = s.length() - 1;
    while (left < right) {
        while (left < right && !Character.isLetterOrDigit(s.charAt(left)))
            left++;                       // skip non-alphanumeric
        while (left < right && !Character.isLetterOrDigit(s.charAt(right)))
            right--;                      // skip non-alphanumeric
        if (Character.toLowerCase(s.charAt(left)) !=
            Character.toLowerCase(s.charAt(right)))
            return false;
        left++;
        right--;
    }
    return true;
}

Anti-Patterns

Wrong: Using two pointers on unsorted data for pair-sum

# BAD -- two-pointer converging requires sorted input
def two_sum_wrong(nums, target):
    left, right = 0, len(nums) - 1
    while left < right:
        s = nums[left] + nums[right]
        if s == target:
            return [left, right]
        elif s < target:
            left += 1
        else:
            right -= 1
    return []  # may miss valid pairs because array is unsorted

Correct: Sort first or use a hash map

# GOOD -- sort first, then use converging pointers
def two_sum_correct(nums, target):
    indexed = sorted(enumerate(nums), key=lambda x: x[1])
    left, right = 0, len(indexed) - 1
    while left < right:
        s = indexed[left][1] + indexed[right][1]
        if s == target:
            return [indexed[left][0], indexed[right][0]]
        elif s < target:
            left += 1
        else:
            right -= 1
    return []

# OR: use a hash map for O(n) without sorting
def two_sum_hashmap(nums, target):
    seen = {}
    for i, n in enumerate(nums):
        complement = target - n
        if complement in seen:
            return [seen[complement], i]
        seen[n] = i
    return []

Wrong: Not skipping duplicates in 3Sum

# BAD -- produces duplicate triplets
def three_sum_wrong(nums, target=0):
    nums.sort()
    result = []
    for i in range(len(nums) - 2):
        left, right = i + 1, len(nums) - 1
        while left < right:
            s = nums[i] + nums[left] + nums[right]
            if s == target:
                result.append([nums[i], nums[left], nums[right]])
                left += 1
                right -= 1
            elif s < target:
                left += 1
            else:
                right -= 1
    return result  # contains duplicate triplets

Correct: Skip duplicate values after finding a match

# GOOD -- skip duplicates at all three positions
def three_sum_correct(nums, target=0):
    nums.sort()
    result = []
    for i in range(len(nums) - 2):
        if i > 0 and nums[i] == nums[i - 1]:  # skip duplicate i
            continue
        left, right = i + 1, len(nums) - 1
        while left < right:
            s = nums[i] + nums[left] + nums[right]
            if s == target:
                result.append([nums[i], nums[left], nums[right]])
                while left < right and nums[left] == nums[left + 1]:
                    left += 1   # skip duplicate left
                while left < right and nums[right] == nums[right - 1]:
                    right -= 1  # skip duplicate right
                left += 1
                right -= 1
            elif s < target:
                left += 1
            else:
                right -= 1
    return result

Wrong: Returning meeting point as cycle start

# BAD -- returns the meeting point, NOT the cycle start
def find_cycle_start_wrong(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            return slow  # this is the meeting point, NOT the cycle start
    return None

Correct: Reset one pointer to head after detection

# GOOD -- Floyd's algorithm phase 2: find the cycle entry
def find_cycle_start_correct(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            # Phase 2: move one pointer to head, advance both by 1
            slow = head
            while slow != fast:
                slow = slow.next
                fast = fast.next
            return slow  # this is the actual cycle entry node
    return None

Common Pitfalls

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Finding a pair in a sorted array with a target sumArray is unsorted and you cannot afford O(n log n) sortHash map (O(n) time, O(n) space)
Removing duplicates or elements in-place from a sorted arrayYou need to preserve original indices or order of unsorted elementsFilter into a new array
Detecting cycles in a singly linked listWorking with a doubly linked list or graphHash set / DFS with visited set
Checking if a string is a palindromeNeed to find all palindromic substringsExpand-from-center or Manacher's algorithm
Merging two sorted arraysArrays are unsortedSort first, or use a heap for k-way merge
Partitioning an array (Dutch National Flag)Need stable partitioning (preserve relative order)Stable partition with O(n) extra space
Container with most water / trapping rain waterProblem has non-monotonic objective with no greedy eliminationDynamic programming or monotonic stack

Important Caveats

Related Units