Introduction to Two Pointers Pattern
The Two Pointers Pattern
When a problem asks you to find a pair (or triple) of elements in a sorted array that
satisfy some condition, the naive move is two nested loops — test every pair, O(n²).
The two-pointers pattern collapses that to one linear pass: place two indices on the array and
move them toward each other, and because the array is sorted, each move lets you rule out a
whole set of pairs at once instead of one at a time.
There are two flavours of the pattern:
- Converging (opposite ends) —
leftat the start,rightat the end, walking inward. Needs a sorted array for pair-sum and its relatives. Also used for container-with-most-water and palindrome checks — container-with-most-water is the odd one out: it needs no sorting, and its safety argument differs from pair-sum's (always move the shorter line: the area is capped by the shorter height, so keeping the shorter side while the width shrinks can never produce a bigger container). This page focuses here. - Fast / slow (same direction, different speeds) — two pointers advancing at different rates on the same end. Used for cycle detection (Floyd), finding the middle of a linked list, and in-place dedup. No sortedness required.
The mechanism (converging)
Keep left and right. Compute the quantity you care about (say the sum).
Compare it to the target: if you need it bigger, the only useful move is left++
(the next value up); if you need it smaller, right--. One comparison, one pointer
step — and everything you skipped is provably out of range because the array is ordered.
Recognize the pattern — the tells
- You need a pair / triple / subarray that meets a condition, and the array is sorted (or you can sort it).
- The obvious solution is a nested loop over all pairs —
O(n²). - The quantity is monotonic in the pointer positions: moving a pointer predictably raises or lowers it. That monotonicity is what makes discarding a whole side safe.
Traced example — pair with target sum 15
Array [1, 3, 4, 6, 8, 11, 15], target 15:
| left | right | arr[left]+arr[right] | vs 15 | action |
|---|---|---|---|---|
| 0 | 6 | 1 + 15 = 16 | too big | right → 5 |
| 0 | 5 | 1 + 11 = 12 | too small | left → 1 |
| 1 | 5 | 3 + 11 = 14 | too small | left → 2 |
| 2 | 5 | 4 + 11 = 15 | match | return (2, 5) |
Code
int[] pairWithTargetSum(int[] arr, int target) {
int left = 0, right = arr.length - 1;
while (left < right) {
int sum = arr[left] + arr[right];
if (sum == target) return new int[]{left, right};
if (sum < target) left++; // need a bigger sum
else right--; // need a smaller sum
}
return new int[]{-1, -1};
}
Complexity — from first principles
left only ever increases and right only ever decreases, and they stop
when they meet. So the total number of moves is bounded by the gap they start with:
moves ≤ right - left = n - 1 → time = O(n)
extra memory = two indices → space = O(1)
Compare the brute force: (n choose 2) ≈ n²/2 pairs, O(n²).
Two pointers is a full order faster, and unlike the hash-set approach (also O(n) time) it uses
O(1) space — the trade you make is that the array must be sorted.
Pitfalls
- Unsorted input. The monotonic argument only holds on sorted data. On unsorted input you
must sort first (adds
O(n log n)) or switch to a hash set. - Pointer crossing. Loop while
left < right. Using≤can pair an element with itself. - Duplicates (3Sum family). After fixing one element and two-pointering the rest, skip equal neighbours or you will emit duplicate triples.
- Wrong pointer moved. The move direction comes from the monotonic relationship — always be able to say why moving this pointer helps, or you will loop forever.
When to use it — and when not
- vs. nested loops: two pointers is O(n) vs O(n²). Always prefer it when the array is sorted and the quantity is monotonic.
- vs. hash set: a hash set also finds pair-sums in O(n) time but costs O(n) space and works on unsorted input. Choose two pointers when the array is already sorted (or sorting is cheap/needed anyway) and you want O(1) space.
- vs. sliding window: sliding window is for contiguous subarrays; two pointers is for pairs at a distance (converging) or traversal invariants (fast/slow). If the answer is a contiguous run, reach for sliding window instead.
- Don't use converging two pointers when the quantity is not monotonic in the pointers (then moving a pointer tells you nothing) — that is a different technique.
Takeaways
- Two pointers turns O(n²) pair-search into O(n) by discarding a whole side per comparison.
- The move rule is the monotonicity argument — state why the discard is safe.
- Converging needs a sorted array; fast/slow does not.
- O(n) time, O(1) space — beats the hash-set approach on memory.
Play with it
Step through the converging pointers on a sorted array yourself — press Play, predict each move (left++ or right−−), and watch the window shrink:
L0 · Two pointers turns O(n²) pair-searches into O(n) time and O(1) space by using array sortedness to discard sub-ranges.
L1 · ⑤ Adversary/Edge — “The array is sorted, but it contains duplicate values, and we need to find all unique pairs that sum to the target. How do you avoid yielding duplicate pairs?”
Trap: Keep a hash set of seen pairs and check against it.
Bar: Skip duplicate elements on both sides by incrementing left and decrementing right until they point to different values after finding a match, maintaining O(1) auxiliary space. Pair with Target Sum
L2 · ② Failure — “You are given a singly linked list. Find if it contains a cycle without using extra memory.”
Trap: We cannot use two pointers because we cannot sort a linked list or traverse backward.
Bar: Use Floyd's cycle-finding algorithm: advance a fast pointer by two steps and a slow pointer by one step; if they meet, a cycle exists, achieving O(n) time and O(1) space. Introduction to LinkedList (Floyd fast/slow)
L3 · ③ Scale — “The array is too large to fit in memory and is streamed from disk. The stream is sorted. How do you find a target sum pair?”
Trap: Load the stream into a distributed key-value store and perform standard lookup.
Bar: Maintain two file pointers or network cursors (one starting from the stream beginning, one from the end via random disk seek) and perform converging checks, reading only one element per step to maintain O(1) memory. Pair with Target Sum
L4 · ① Concurrency — “Multiple reader threads are querying target sum pairs on the same shared read-only sorted array concurrently. How do you optimize execution?”
Trap: Wrap the two-pointer loop in a mutex lock.
Bar: Do not share the index pointers; allocate independent left and right stack variables per thread while keeping the array read-only, allowing lock-free concurrent execution.
L5 · ⑥ Cost/Simplicity — “If sorting the array first takes O(n log n) time, is two pointers still better than a hash map lookup (O(n) time and space) on an unsorted array?”
Trap: Yes, because two pointers is always faster.
Bar: Use a hash map if you perform a single query on unsorted data. Reach for sorting plus two pointers only if you need to run multiple queries (amortizing the sort cost) or if memory constraints prohibit O(n) auxiliary space. Hashtables across languages (one-pass two-sum)
The floor keeps dropping: How do you solve 3-Sum (a + b + c = 0) with two pointers, and can it be done in less than O(n²) time?
Self-locate: died at L1 → you present mid-level; L4+ → staff signal.
Facing any new concept? Hit it with the six: concurrent? failing? at 100×? over time? adversarial? worth the cost? — that's the interviewer's whole playbook.
Re-authored and deepened for this guide. Sources: DesignGurus “Grokking the Coding Interview” (pattern framing) and standard first-principles treatment of the monotonic-discard argument and complexity.
🤖 Don't fully get this? Learn it with Claude
Stuck on Introduction to Two Pointers Pattern? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Build the mental picture, not memorization.
I just read a lesson on **Introduction to Two Pointers Pattern** (DSA) and want to truly understand it. Explain Introduction to Two Pointers Pattern from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Socratic — adapts to where you're stuck.
Teach me **Introduction to Two Pointers Pattern** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Active recall exposes what you missed.
Quiz me on **Introduction to Two Pointers Pattern** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Intuition + hook + flashcards for long-term memory.
Help me remember **Introduction to Two Pointers Pattern** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.