Two Pointers (Opposite Ends)
Walk one pointer in from each end of a sorted (or monotone-bounded) array, moving whichever side cannot improve the answer.
Overview
Two pointers from opposite ends replaces a nested O(n²) pair scan with a single O(n) pass. Start l = 0, r = n - 1, look at the pair (a[l], a[r]), and use a monotonic property of the input to decide which pointer to move inward. Each step discards one index for good, so the loop runs at most n - 1 times.
The technique needs an ordering that makes one move provably safe. For pair sums that ordering is sortedness; for container-with-most-water-style problems it is that the width only shrinks, so the shorter wall is the one worth abandoning.
Intuition
A mental model before the formal terms.
Picture a sorted row of numbered cards. Pick up the leftmost (smallest) and rightmost (largest). If their sum is too small, the *only* way to raise it is to swap out the smallest card — the largest is already the biggest thing available. If the sum is too big, swap out the largest. You never need to look back at a card you dropped.
For the water container, think of two walls and the water between them. Moving either wall inward makes the pool narrower. If you move the taller wall the height is still capped by the shorter one, so the area can only fall. Moving the shorter wall is the only move that might help.
How it works
- Sort the input if the problem does not guarantee order (
O(n log n), and only if indices need not be preserved — otherwise sort pairs of(value, index)). - Initialize
l = 0,r = n - 1. - While
l < r: evaluate the candidate formed bya[l]anda[r]. - If the candidate is exactly what you want, record it. Then move a pointer (or both) to look for further answers.
- If the candidate is "too small",
l++; if "too big",r--. The direction is determined by which endpoint is the binding constraint. - Stop when the pointers meet. Every pair was either examined or ruled out by an earlier move.
Why it works
Invariant: the answer, if it exists, uses indices in [l, r]. Initially that is the whole array. When a[l] + a[r] < target, every pair (l, j) with j ≤ r also sums to less than target because a[j] ≤ a[r]. So index l cannot participate in any answer with anything still in range and can be dropped. The symmetric argument justifies r-- when the sum is too large. The invariant is preserved, and when l == r no pair remains.
For maximum-area-style problems the argument is about elimination of a *whole set* of pairs: if h[l] < h[r], every container (l, j) for l < j < r has height ≤ h[l] and width < r - l, so all of them are worse than (l, r) which was already measured. Dropping l loses nothing.
Each iteration moves at least one pointer by one, and pointers never move outward, so there are at most n - 1 iterations: O(n) after any sort.
Recognition
How to tell a problem wants this.
- The input is sorted, or the problem lets you sort it (order of output does not matter, or you may return values instead of indices).
- You are asked for a pair (or triple, via an outer loop) meeting a sum/difference/product condition: "two numbers that add up to", "closest to target", "count pairs with sum less than k".
- Phrases like "in-place", "O(1) extra space", "without using a hash map", or "the array is sorted in non-decreasing order" — the sortedness hint is the interviewer telling you not to reach for Hash Map.
- Symmetric problems: "is it a palindrome", "reverse in place", "container / trapping water" — one pointer from each end walking toward the middle.
- Constraints of
n ≤ 10^5with an obviousO(n²)brute force point at either two pointers or hashing; if the array is sorted, two pointers is theO(1)-space option.
Interactive visualization
Play, step, change the input. ← → and space work too.
1lo = 0, hi = n - 12while lo < hi:3 s = a[lo] + a[hi]4 if s == target: return (lo, hi)5 if s < target: lo += 16 else: hi -= 17return not foundPseudocode
1l = 0, r = n - 12while l < r:3 s = a[l] + a[r]4 if s == target: return (l, r)5 if s < target: l = l + 1 # a[l] is too small for any partner ≤ a[r]6 else: r = r - 1 # a[r] is too big for any partner ≥ a[l]7return noneImplementations
1# Two Sum II: 1-based indices of two numbers in a sorted array summing to target2def two_sum_sorted(a: list[int], target: int) -> list[int]:31 · Initialize pointers at both ends4 l, r = 0, len(a) - 152 · Squeeze while the pointers have not met6 while l < r:73 · Evaluate the current pair (64-bit to avoid overflow)8 s = a[l] + a[r] # Python ints never overflow9 if s == target:10 return [l + 1, r + 1]114 · Move the pointer that cannot improve the answer12 if s < target:13 l += 114 else:15 r -= 1165 · No pair found17 return [-1, -1]- Tuple assignment
l, r = 0, len(a) - 1initialises both pointers in one statement. - Python integers are arbitrary precision, so
a[l] + a[r]can never overflow — the comment marks where other languages need care. - Returning a
list[int]matches the LeetCode signature; a tuple would be the more Pythonic choice for a fixed pair. - Explicit
+= 1/-= 1because Python has no++operator. - The final
return [-1, -1]keeps the function total when no pair exists.
sorted(a)returns a new list (O(n) memory);a.sort()sorts in place — use the latter if the caller allows mutation.- Type hint
list[int]needs Python 3.9+; useList[int]fromtypingon older versions. enumerateis unnecessary here because both pointers are explicit indices.
- Writing
while l <= rand matching an element with itself. - Sorting the input when the problem wants original indices back.
- Reaching for a dict (classic Two Sum) when the sorted guarantee makes O(1) space possible.
- Overflow on
a[l] + a[r]: C++intoverflow is undefined behaviour (uselong long); JavaScript/TypeScript are exact only up to 2^53; Python integers never overflow. - Empty input: C++
a.size() - 1issize_tand wraps; JS/TS/Python produce-1and thewhile (l < r)guard handles it. - Sorting first: JS/TS
sort()compares as strings by default; C++std::sortand Pythonsort()compare numerically. - Return shape: C++ returns a heap-allocated
vector<int>; TS can express the fixed tuple[number, number]; Python would normally return a tuple.
Complexity
Plus O(n log n) if the input must be sorted first. Three Sum wraps this in an outer loop for O(n²).
Compare growth rates in the Complexity Explorer →When to use — and when not to
- Sorted array and a pair/triple condition on values (sum, difference, closeness to a target).
- Problems with a two-sided geometric structure: palindromes, reversing, containers, trapping rain water.
- When
O(1)extra space is required and a hash-map solution would useO(n). - Counting pairs satisfying an inequality — when
a[l] + a[r] < k, allr - lpairs(l, j)count at once.
- Unsorted input where original indices must be returned and sorting is not allowed — use a Hash Map (classic Two Sum).
- The decision rule is not monotone: if a larger
a[r]could make the condition *either* more or less satisfied, dropping an end is unsafe. - Linked lists without random access to the tail — the same idea needs an
O(n)reversal or a stack. - When the pair condition is on indices (subarray, window) rather than values — that is Sliding Window (Variable Size) or Two Pointers (Same Direction).
Alternatives
Common mistakes
- Forgetting to sort, or sorting when the answer must be original indices.
- Using
while l <= rfor a pair problem — pairs the same element with itself. - In Three Sum, not skipping duplicate values after finding a triple, producing repeated answers.
- Moving the *taller* wall in the container problem "because it looks promising" — that direction can only lose area.
- Integer overflow on
a[l] + a[r]in fixed-width languages when values approach2^31.
Interview patterns
- Two Sum II, then Three Sum / Four Sum by fixing one element and running two pointers on the rest.
- Three Sum Closest: track the minimum
|sum - target|while squeezing. - Container With Most Water and Trapping Rain Water (opposite pointers plus running left/right maxima).
- Valid Palindrome / palindrome with at most one deletion — on mismatch, try skipping either side.
- Count pairs with sum less than
k: whena[l] + a[r] < k, addr - land advancel. - Squares of a sorted array: fill the output from the back with whichever end has larger absolute value.
- Recognizing the approach from an array and a targetIntermediate
- When space complexity mattersIntermediate
- Two pointers or hash map?Intermediate
- Convincing me your algorithm is correctExpert
- Two SumBeginner