DSA · Arrays · Pattern
Two pointers: a nested loop collapsed into one pass
On a sorted array, one index at each end and a single rule about the sum find a target pair in one sweep. You will trace it by hand on 1 3 4 6 8, then step it against the brute-force loop.
Walk the pointers step by step →01 The idea
Two indices walking toward each other
You are given a sorted array and a target. The obvious plan is to try every pair: a loop inside a loop. It works, but on a list of 1000 numbers that is roughly half a million checks, and the cost grows as the square of the length.
The two-pointer plan looks at one pair only. Put L on the first element and R on the last, add them, and compare that sum with the target. Whatever the answer is, you learn enough to throw one element away for good. The array is sorted, so a[L] is the smallest value still in play and a[R] is the largest. A sum that falls short can only be rescued by a bigger left value. A sum that overshoots can only be saved by a smaller right value.
02 Hand trace
Find a pair summing to 10 in 1 3 4 6 8
The array is 1 3 4 6 8 and the target is 10. L starts at index 0 (value 1), R starts at index 4 (value 8). The two rose cells are the pair being added on that row. Check every sum yourself before you read the note beside it.
Four comparisons. The nested loop needs eight on this exact list, and ten if the pair had not existed at all. Look at what each row bought you: row one did not merely fail, it proved that the value 1 can never reach 10 with any partner, because 8 was already the biggest partner on offer. One comparison, one element eliminated forever — that is the whole pattern.
Had the target been 9 instead, the first look at 1 and 8 would have landed straight away. That is the lucky case, not the lesson. The lesson is what you do when the first look misses.
03 The code
From two loops to one walk
Both versions answer the same question and return the same pair. The difference is what happens after a failed comparison: the nested loop learns nothing, while the walk removes a candidate from the problem.
Naive · nested loop
for i = 0 to n-2: for j = i+1 to n-1: if a[i] + a[j] == target: return (i, j)return "no pair"
Improved · two pointers
L = 0; R = n - 1while L < R: s = a[L] + a[R] if s == target: return (L, R) if s < target: L = L + 1 else: R = R - 1return "no pair"
Sorted input became a requirement, not a detail. The nested loop runs on any order at all. The walk reads a[L] as the smallest live value and a[R] as the largest, and that sentence is only true on a sorted array. Hand the improved version an unsorted list and it will report "no pair" for inputs that have one.
One index moves per comparison, instead of a whole inner loop finishing. In row 1 of the trace, 1 + 8 = 9 fell short and index 0 was gone. The nested loop had to pair 1 with all four partners before it gave up on 1, and even then it knew nothing about 3.
The loop condition is now while L < R. L only rises and R only falls, so the window loses exactly one element per comparison. That is the proof the walk terminates, and the proof that it runs at most n-1 comparisons.
05 Cheat sheet
The numbers you will be asked for
| Case | Cost | Why |
|---|---|---|
| Two pointers, best case | O(1) | The pair sits at the two ends, so the first comparison lands. Target 9 on 1 3 4 6 8 does exactly this. |
| Two pointers, average and worst | O(n) | Each comparison moves L up or R down, so at most n-1 of them happen before the pointers meet. |
| Extra space | O(1) | Two integer indices. Nothing is copied and nothing is allocated. |
| Input not sorted yet | O(n log n) | The sort dominates the linear scan that follows it. |
| Nested loop, worst case | O(n²) | All n(n-1)/2 pairs get checked. That is 10 pairs at n=5 and 4950 at n=100. |
06 Where & why
When two pointers is the right answer
Two pointers is not a general search tool. Its value is narrow and specific: it turns a nested loop into one pass, but only when the data is ordered in a way that lets a failed comparison eliminate a candidate. Without that ordering, the pattern does not apply at all.
Reach for it when…
Use something else when…
| Situation | Better choice | Why |
|---|---|---|
| The input is unsorted and you need one answer | Hash set, one pass | O(n) time without paying O(n log n) to sort. It costs O(n) memory in exchange. |
| You must return the original indexes, as in LeetCode Two Sum | Hash map, value → index | Sorting destroys the original positions. Pairing each value with its index before sorting also works, but the hash map is shorter. |
| The condition is not monotone, for example a[i] XOR a[j] == k | Hash map or a bit trie | Moving a pointer no longer changes the result in a known direction, so the discard rule is simply invalid. |
| Data arrives as an unbounded stream | Hash map with counts | You cannot sort what has not arrived yet, and the walk needs both ends up front. |
07 Interview questions
Answers you can say out loud in twenty seconds
These run in the order an interview usually escalates: what it is, why the discard rule is safe, what it costs, then the alternatives and the edge cases.
What is the two-pointer technique?
Why does moving L right increase the sum and moving R left decrease it?
The sum is too small. Why is it safe to throw away a[L] completely?
What is the time complexity, and why exactly?
What is the space complexity?
What if the array is not sorted?
Two pointers or a hash table for two-sum?
How do you return every pair that hits the target, not just the first?
What happens if L and R meet without finding anything?
Does it still work with duplicate or negative values?
Where does this pattern actually show up?
Would you ever ship the nested loop instead?
08 Practice problems
Six problems, in rising order
Every one is solvable with the rule from section 01 and nothing else. Before you write any code, say the discard argument out loud: which element does a failed comparison eliminate, and why.