Lesson 034 min read

Two Pointers

November 05, 2026

// TL;DR

When an array is SORTED, you often don't need to check every pair — put one pointer at the left, one at the right, and move them inward based on the comparison. If the pair sum is too small, move LEFT up (to increase it); too big, move RIGHT down. Each step eliminates a position, so you scan once: O(n) time, O(1) space, versus brute-force O(n²). Signals: 'sorted array' + 'find a pair/triplet' or 'in place'. Worked examples: sorted two-sum, valid palindrome (pointers from both ends), and 3Sum (fix one element, two-pointer the rest). Also powers reverse-in-place, remove-duplicates and container-with-most-water.

Two pointers is the first pattern to master — it is simple, it appears constantly, and it turns many O(n²) brute forces into clean O(n) solutions.

The idea

Two pointers, converge from both ends. On a sorted array 1, 3, 4, 5, 7, 11 with a left pointer L moving right and a right pointer R moving left, for target 9 we have a[L] plus a[R] equals 4 plus 5 equals 9, a match. If the sum is too small move L up, too big move R down. The idea: when the array is sorted you don't need to try every pair; start one pointer at the left and one at the right, and if the sum is too small the only way to grow it is to move left inward, if too big move right inward; each step removes a pointer position so you scan the array once in O(n) time and O(1) space versus brute-force O(n squared). Use it for sorted two-sum, 3-sum, valid palindrome, reverse in place, remove duplicates, container with most water.

On a sorted array, you don’t need the O(n²) brute force of checking every pair. Put one pointer at the left and one at the right. Look at the two values:

  • If their sum is too small, the only way to increase it is to move the left pointer rightward (to a bigger value).
  • If too big, move the right pointer leftward (to a smaller value).
  • If it’s exactly the target, you’re done.

Each step removes one position from consideration, so you traverse the whole array once: O(n) time, O(1) space.

The template

def two_sum_sorted(a, target):
    i, j = 0, len(a) - 1
    while i < j:
        s = a[i] + a[j]
        if s == target:
            return [i, j]
        if s < target:
            i += 1          # need a bigger sum → move left pointer up
        else:
            j -= 1          # need a smaller sum → move right pointer down
    return []
# two_sum_sorted([1,3,4,5,7,11], 9) -> [2, 3]   (4 + 5)

Worked example: valid palindrome

Two pointers also works from both ends toward the middle — comparing as they go:

def is_palindrome(s):
    s = [c.lower() for c in s if c.isalnum()]
    i, j = 0, len(s) - 1
    while i < j:
        if s[i] != s[j]:
            return False
        i += 1; j -= 1
    return True
# is_palindrome("A man, a plan, a canal: Panama") -> True

Worked example: 3Sum

Many “find a triplet” problems are “fix one element, then two-pointer the rest.” Sort first, fix nums[i], and two-pointer the remaining subarray for the complement -nums[i]:

def three_sum(nums):
    nums.sort()
    res = []
    for i in range(len(nums) - 2):
        if i > 0 and nums[i] == nums[i-1]:        # skip duplicates
            continue
        l, r = i + 1, len(nums) - 1
        while l < r:
            s = nums[i] + nums[l] + nums[r]
            if s == 0:
                res.append([nums[i], nums[l], nums[r]])
                while l < r and nums[l] == nums[l+1]: l += 1   # skip dup
                while l < r and nums[r] == nums[r-1]: r -= 1
                l += 1; r -= 1
            elif s < 0: l += 1
            else:       r -= 1
    return res
# three_sum([-1,0,1,2,-1,-4]) -> [[-1,-1,2],[-1,0,1]]

3Sum is O(n²): an outer loop (n) with a two-pointer inner pass (n). Sorting is O(n log n), dominated by the n².

Step-by-step walkthrough

Trace Container With Most Water on height = [1, 8, 6, 2, 5, 4, 8, 3, 7]. The two pointers start at the ends; each step computes the area and moves the shorter wall inward, because moving the taller one can never produce a bigger area.

Step left right h[left] h[right] area best move
1 0 8 1 7 8 × 1 = 8 8 left → (shorter)
2 1 8 8 7 7 × 7 = 49 49 right ← (shorter or equal)
3 1 7 8 3 6 × 3 = 18 49 right ← (shorter or equal)
4 1 6 8 8 5 × 8 = 40 49 right ← (shorter or equal)
5 1 5 8 4 4 × 4 = 16 49 right ← (shorter or equal)
6 1 4 8 5 3 × 5 = 15 49 right ← (shorter or equal)
7 1 3 8 2 2 × 2 = 4 49 right ← (shorter or equal)
8 1 2 8 6 1 × 6 = 6 49 right ← (shorter or equal)

The pointers meet after 8 steps — one pass, O(n) — and the answer is 49 (found when left=1, right=8). A brute-force double loop would have checked all 36 pairs.

When to reach for it

Signals that two pointers fits: the array is sorted (or you can sort it), and you need to find a pair/triplet, reverse or rearrange in place, or compare from both ends. It is also the “in-place” workhorse: removing duplicates from a sorted array, moving zeros, partitioning.

Pitfalls

  • Forgetting the array must be sorted for the sum-based version. If it isn’t sorted and you can’t sort it, use a hash map instead (Lesson 6).
  • Infinite loops / off-by-one. Always advance at least one pointer each iteration, and use while i < j (not <=) for pair problems.
  • Duplicate handling in 3Sum. Skip equal neighbours or you’ll emit duplicate triplets — a classic bug.

Recap

On a sorted array, two pointers converging from the ends solves pair/triplet problems in O(n) (or O(n²) for 3Sum) instead of O(n²)/O(n³): move the left pointer up when the sum is too small, the right down when too big. It also handles two-ends comparison (palindrome) and in-place rearrangement. Signal: sorted + find a pair / in place.

Practice: 12 solved problems on two pointers → — cover each one, solve it yourself, then check the tested code.

Next: Lesson 4 — Sliding Window, two pointers’ close cousin for contiguous sub-arrays.

exercisePractice this patternTwo Pointers — Practice Problemsarrow_forward