TL;DR: Two pointers replaces "compare every pair" brute force, which is O(n²), with a single O(n) pass by moving two indices through the data under a rule that never needs to back up. There are three templates worth knowing: converging (start at both ends, move inward, powered by sorted order), reader-writer (one pointer reads, one writes, for in-place array edits), and two-sequence (one pointer per input, for merging and matching). Learn the three templates and the one-line reason each pointer never moves backward, and a large family of easy-to-medium interview problems becomes mechanical.
This is the second deep dive in our pattern series, following sliding window. Two pointers comes right behind it for a reason: the two patterns are siblings, interviewers love to probe whether you know which one a problem calls for, and two pointers is usually the first pattern where candidates experience the "wait, that's just O(n)?" moment that makes pattern-based prep click.
The tell: how to recognize two pointers in an interview
You should suspect two pointers when the problem involves pairs, symmetry, or in-place rearrangement in an array or string:
- "find a pair (or triplet) that sums to a target" in a sorted array
- "is this a palindrome" or anything comparing a sequence to its reverse
- "remove/move elements in place without extra space"
- "merge these two sorted arrays" / "is A a subsequence of B"
- "container with most water," "trapping rain water," and other from-both-ends geometry
The strongest signal is the word sorted next to a pair-finding question, followed closely by the phrase in place. And note what's not required: unlike sliding window, the answer doesn't need to be a contiguous chunk. Two pointers is about the positions of the pointers; sliding window is about the contents between them. If you're maintaining state about everything between your two indices, you've crossed into sliding window territory (more on that boundary below).
The pattern exists because these problems have a brute force that compares every pair: two nested loops, O(n²). But when the data has structure (sorted order, symmetry, or a stable partition rule), each comparison lets you permanently discard a candidate. Two pointers is just bookkeeping for that discarding: every step moves one pointer forward, no pointer ever backs up, so the total work is O(n).
Template 1: converging pointers
Start one pointer at each end; move them toward each other until they meet. This is the template for sorted pair-finding and symmetry checks.
def pair_with_target_sum(arr, target): # sorted input
left, right = 0, len(arr) - 1
while left < right:
s = arr[left] + arr[right]
if s == target:
return [left, right]
if s < target:
left += 1 # need a bigger sum
else:
right -= 1 # need a smaller sum
return [-1, -1]
The line that interviewers actually care about is the justification: when the sum is too small, no pair involving arr[left] can ever work, because arr[right] is already the largest available partner. So left can be discarded forever. Each iteration eliminates one element from consideration; n elements, O(n) total. Say that argument out loud in your interview. The code is easy; the discard argument is the pattern.
The same template solves Valid Palindrome (compare, move both inward), 3Sum (fix one element, run this template on the rest), and Container With Most Water, where the discard rule is "move the shorter line, because keeping it can only ever shrink the area."
Template 2: reader-writer (same direction)
Both pointers start at the beginning. One scans the array (the reader); the other marks where the next kept element belongs (the writer). This is the template for every "modify in place with O(1) space" problem.
def move_zeroes(nums): # LeetCode 283
write = 0
for read in range(len(nums)):
if nums[read] != 0:
nums[write], nums[read] = nums[read], nums[write]
write += 1
The invariant to state: everything before write is finalized output; everything between write and read is known-discardable. Remove Duplicates from Sorted Array, Remove Element, and String Compression are this exact code with a different keep-condition.
The famous escalation is the Dutch National Flag problem (Sort Colors): partition an array of 0s, 1s, and 2s in one pass. It uses three pointers (low, mid, high) but the idea is identical: maintain regions with invariants, shrink the unknown region one step at a time. It's in the Two Pointers section of Grokking 75 because interviewers reach for it constantly.
Template 3: two sequences
One pointer per input; advance whichever pointer the comparison tells you to. This is how you merge two sorted arrays, check Is Subsequence, and compare strings with edge cases.
def is_subsequence(s, t): # LeetCode 392
i = 0
for ch in t:
if i < len(s) and s[i] == ch:
i += 1
return i == len(s)
A sneaky variation worth knowing: walk from the ends. Comparing Strings Containing Backspaces asks whether two typed strings (where # is backspace) are equal; processing forward requires building buffers, but walking both strings backward with one pointer each, skipping backspaced characters as you go, does it in O(1) space. Interviewers use this problem specifically to see whether you'll notice that reversal.
Want each variation taught, not just listed? The two pointers chapter of Grokking the Coding Interview walks through a dozen problems on this template with visual explanations and code in six languages, then does the same for all 42 patterns (32 common + 10 advanced).
Two pointers vs sliding window: the boundary
This distinction earns real points in interviews, so here it is precisely. Both patterns move two indices forward through data in O(n). The difference is what you track:
- Two pointers: decisions come from the elements at the pointers. No state about what's between them. (Pair with target sum: you only ever look at
arr[left]andarr[right].) - Sliding window: decisions come from aggregate state between the pointers: a sum, a frequency map, a count. The window's contents are the whole point.
So "longest substring without repeating characters" is sliding window (you track the set of characters inside), while "two sum on a sorted array" is two pointers (you track nothing). If you catch yourself building a hash map of the middle, switch templates; the sliding window guide has that machinery.
The three mistakes that sink candidates
1. Converging on unsorted data. The discard argument depends on sorted order; on unsorted input it's just wrong, and interviewers plant this trap deliberately (classic Two Sum is unsorted, which is why its answer is a hash map, not two pointers). If sorting first is allowed and indices don't need preserving, sort. If the problem wants original indices, sorting destroys them; notice that before coding, not after.
2. A branch that doesn't move a pointer. Every path through your loop body must advance left, right, or both, or while left < right becomes an infinite loop. This bites hardest in 3Sum-style problems with duplicate-skipping logic. Trace one iteration of every branch before you declare done.
3. Fumbling duplicates in 3Sum. After finding a valid triplet, you must skip repeated values on both sides (while left < right and arr[left] == arr[left - 1]: left += 1), or your output has duplicate triplets. This is the most common correctness bug in the most-asked two pointers problem; practice it until the skip lines are automatic.
Practice ladder
In order; each rung adds one wrinkle. The middle four are the Two Pointers section of Grokking 75.
- Valid Palindrome (E): pure converging template.
- Pair with Target Sum (E): converging with the discard argument; rehearse saying why it works.
- Move Zeroes (E): pure reader-writer.
- Dutch National Flag / Sort Colors (M): three-pointer partitioning with invariants.
- Comparing Strings Containing Backspaces (M): two sequences, walked backward.
- Minimum Window Sort (M): converge from both ends to find the unsorted middle.
- 3Sum (M): fix-one-element plus converging, with duplicate skipping. The most-asked problem in this pattern.
- Stretch goals: Container With Most Water (M), Trapping Rain Water (H), the pattern's final boss, where each pointer carries a running max from its own side.
Then write the tell in your own words ("I should suspect two pointers when ___") and move on. Recognition, not volume, is the skill, and it's the difference we keep coming back to in Why Is LeetCode So Hard?
The takeaway
Two pointers is one idea, monotonic discarding, expressed in three templates: converge from the ends when order or symmetry gives you a discard rule, read-and-write in one direction when editing in place, one pointer per sequence when merging or matching. Know the templates, know the boundary with sliding window, and be ready to justify why a discarded element can never matter, because that justification is what the interviewer is actually grading. Next in the series: Fast & Slow Pointers, where the two pointers move at different speeds and the payoff is cycle detection.
Go deeper: Grokking the Coding Interview teaches all 42 patterns (32 common + 10 advanced), including full chapters on two pointers and fast & slow pointers, with 300+ problems in six languages. Short on time? Grokking 75 covers the essential problems inside a 6-week plan.
FAQs
What is the two pointers technique in simple terms? It's a way to scan data with two indices instead of two nested loops. Because each comparison lets you permanently rule out one element, both indices only ever move forward, and the whole scan finishes in O(n) instead of O(n²).
When should I use two pointers vs sliding window? Ask what drives your decisions. If it's only the elements at the two indices (a sum of two values, a character comparison), it's two pointers. If it's aggregate state about everything between them (a window sum, a frequency map), it's sliding window. Contiguity is the other clue: sliding window answers are always contiguous chunks; two pointers answers often aren't.
Does the array have to be sorted for two pointers? Only for the converging template, because the discard argument relies on order. Reader-writer and two-sequence templates work on unsorted data. If the input isn't sorted and the problem allows it, sorting first (O(n log n)) then converging is a standard combo, as in 3Sum.
Why is two pointers O(n) when there are two moving indices? Because neither index ever moves backward. Between them they can take at most 2n steps total across the entire run, regardless of how the steps interleave. Being able to state this is worth as much as writing the code.
What are the most common two pointers interview questions? 3Sum is the most frequently asked, at Meta especially. Valid Palindrome, Container With Most Water, Sort Colors, and Trapping Rain Water round out the usual suspects. If you can solve 3Sum with clean duplicate handling and explain the discard argument, you're ready for nearly any two pointers question a loop will throw at you.
