Skip to content
All reading material
Patterns·12 min read

Two Pointers

Converging, same-direction, and two-sequence pointers — turning O(n²) scans into O(n)/O(1) for pairs, in-place partitioning, and merging.


The idea

Replace nested loops with two indices that each move at most n times → O(n) time, O(1) space. Three flavors cover almost everything: pointers that converge from the ends, pointers that chase in the same direction, and pointers walking two sequences at once.

Converging pointers (sorted input)

A left/right pair on a sorted array decides which side to move from the current state:

def two_sum_sorted(nums, target):
    lo, hi = 0, len(nums) - 1
    while lo < hi:
        s = nums[lo] + nums[hi]
        if s == target: return [lo, hi]
        if s < target:  lo += 1        # need a bigger sum
        else:           hi -= 1        # need a smaller sum
    return []

Same shape:

  • Valid palindrome — compare from both ends inward.
  • Container with most water — move the shorter wall inward (it’s the limiting one).
  • Trapping rain water — two pointers tracking left/right max walls, O(1) space.
  • 3Sum / 4Sum — sort, fix one (or two) elements, two-pointer the rest; skip duplicates.

Same-direction (fast/slow, write pointer)

A write pointer trails a read pointer to compact/partition in place:

def remove_duplicates(nums):           # sorted; returns new length
    w = 0                              # write index
    for r in range(len(nums)):
        if r == 0 or nums[r] != nums[r-1]:
            nums[w] = nums[r]; w += 1
        # else skip the duplicate
    return w

This pattern does move zeroes, remove element, and partition (Dutch National Flag: three pointers split <, =, > a pivot in one pass). On linked lists, fast/slow finds the middle and detects cycles (Floyd’s — see the linked-lists primer).

Two sequences at once

Two pointers march through two arrays/lists:

  • Merge two sorted arrays — advance whichever front is smaller.
  • Is subsequence — advance the pattern pointer only on a match; O(n).
  • Intersection of sorted arrays — advance the smaller; collect equal.
  • Merge step of merge sort — the same move (see the sorting / divide-and-conquer primers).

Sliding window is a specialization

A window [left, right] where both pointers move forward is a two-pointer pattern — important enough to have its own primer. Reach for it on “longest/shortest contiguous subarray with a constraint.”

When to reach for it

  • The array is sorted (or sortable) and you need a pair/triplet summing/comparing to a target.
  • You’re removing/partitioning in place (O(1) space).
  • A brute force compares every pair — converging or chasing pointers usually collapse it to one pass.
  • Merging or comparing two ordered sequences.

Pitfalls

  • Unsorted input — converging two-pointer needs sorted data (sorting costs O(n log n), which may or may not beat a hash map’s O(n)/O(n)).
  • Duplicate handling in 3Sum-style problems — skip equal values to avoid duplicate triplets.
  • Off-by-one in the while lo < hi boundary and the write index.

State the trade: “two pointers gives O(1) space if I can sort; a hash map gives O(n) time without sorting” — choosing between them is the signal.