I was reviewing a junior engineer's PR last year for a function that found the closest pair of timestamps in a sorted array. The version they shipped was a nested loop, O(n^2), which on the test data of fifty entries was instant and on the production data of half a million entries timed out the request. The fix was about ten lines: walk two indices toward each other from opposite ends. Once they saw it, they wrote the rest of the family of two-pointer problems without trouble. The block was not the code; the block was recognizing that the problem had two-pointer shape.
The argument I want to make in this short piece is that two pointers is a recognition pattern, not a clever trick. There are three sub-patterns. Each one solves a specific shape of problem, and each one has a canonical example you can keep in your head. Once you can name the sub-pattern from the problem statement, the code is mechanical.
What the technique actually is
Two pointers is the technique of solving array and string problems by maintaining two indices that move through the data with related, monotonic progress, instead of using nested loops. Both pointers usually advance, but in different directions or at different speeds, and the work per step is O(1). The total runtime is O(n) because each pointer crosses the array at most once.
The gain over nested loops is the gain from a hash table over a linear scan: a structural change that drops a factor of n off the runtime. The cost is that the algorithm is harder to read at first, because the two indices encode information that a nested loop spells out.
Sub-pattern 1: opposite ends
Two pointers starting at the two ends of an array, moving toward each other. Used when the array is sorted and you are looking for a pair that satisfies some condition.
The canonical problem is two-sum on a sorted array. Find two indices whose values sum to a target.
The trick is that the array's sortedness lets you decide which pointer to move. If the sum is too small, the only way to grow it is to move lo right. If the sum is too large, the only way to shrink it is to move hi left. Each step rules out an entire row or column of the implicit n^2 pair grid. Total work is O(n).
The family of problems that uses this sub-pattern: three-sum (fix one pointer, two-pointer the rest), container with most water, valid palindrome (move inward, comparing characters), reverse string in place. Whenever the problem says "sorted array" and asks about pairs or relationships across the array, opposite-ends two pointers is the first thing to try.
Sub-pattern 2: same direction
Two pointers walking the same array left to right, but at different speeds or with different roles. Used when you need to filter, compact, or transform an array in place.
The canonical problem is removing duplicates from a sorted array in place, returning the new length.
The read pointer scans every element. The write pointer only advances when a new unique value is found. The two pointers diverge over time: write lags read by exactly the number of duplicates skipped. The same shape solves "move all zeros to the end", "remove element", "merge two sorted arrays into the first one", and a long list of in-place compaction problems.
The key recognition signal is "in place" and "the output has fewer or equal elements than the input". Whenever a problem asks for that, same-direction two pointers is the candidate.
Sub-pattern 3: fast and slow
Two pointers walking the same structure where one moves twice (or k times) per step of the other. Used when the structure has a cycle, a midpoint, or a relative position you need to detect.
The canonical problem is detecting a cycle in a linked list (Floyd's tortoise and hare).
If there is no cycle, fast reaches the end first. If there is a cycle, fast laps slow from behind and they meet inside the cycle. The math is elegant; the practical point is that this is O(n) time and O(1) space, where the obvious hash-set version is O(n) space.
The family of problems: find the middle of a linked list (slow ends at the middle when fast reaches the end), find the kth element from the end (start fast k ahead, walk both, slow lands on the answer), find the start of a cycle (Floyd's algorithm with a second pass). Whenever the problem involves a linked list or stream and asks about position-from-end, midpoint, or cycle, fast-slow is the candidate.
Recognition rules I keep on a sticky note
This is the part that decides whether the technique is useful to you in practice. The codes above are short. Recognizing when to reach for them is the work.
- Sorted array, asking about pairs or sums: opposite ends.
- In-place compaction, filtering, or merging: same direction.
- Linked list with cycle, midpoint, or kth-from-end: fast and slow.
- String palindrome check: opposite ends.
- Subarray with property (sum, count, distinct elements): probably sliding window, which is a cousin of same-direction two pointers.
When I am stuck on an array problem and the obvious solution is O(n^2), two pointers is the first thing I try. About 60 percent of the time, one of these three sub-patterns applies and the solution drops to O(n). The other 40 percent of the time, it is a sliding window or a hash table or a different structure entirely, but the recognition cycle is fast: try two pointers, if the pointers do not have monotonic progress, abandon and try something else.
Try this on your next array problem
Open the next array or string problem you would have brute-forced and ask three questions before writing any code. Is the array sorted, or could I sort it for O(n log n) and still come out ahead? If yes, opposite-ends two pointers is the candidate. Does the problem ask for an in-place transformation that produces an equal-or-shorter output? If yes, same-direction two pointers is the candidate. Is the data a linked list or a stream where I need a relative position? If yes, fast and slow is the candidate.
If the answer is "none of the three", drop the technique and move on. If the answer is "yes" to one of them, write the solution from the canonical example I showed above and adapt the predicate to the problem. The code is short; the win is the recognition. Do this on five problems in a row and the recognition becomes automatic, which is the entire return on the technique.
