Here's a question that looks easy until it isn't.
You're given a sorted list of numbers and a target. Are there two numbers in it that add up to that target?
The obvious move — the one almost everyone writes first — is to check every pair. And it works! Right up until the list has a hundred thousand numbers, your two loops quietly become ten billion operations, and your solution times out.
The maddening part? The list was sorted. You were handed a gift and threw it away.
By the end of this post you'll see how that one fact — sorted — collapses the whole problem into a single pass, and exactly why the trick can never miss the pair it's looking for.
If you can read a for-loop, you're ready. No prior "patterns" knowledge needed — that's the whole point.
First, the loop everyone writes
The instinct is to fix one number and try it against every other number after it:
// the honest first attempt
for (let i = 0; i < nums.length; i++)
for (let j = i + 1; j < nums.length; j++)
if (nums[i] + nums[j] === target) return [i, j];On [1, 3, 4, 6, 8, 11] with target 10, it eventually stumbles onto 4 + 6 and returns happily. The problem isn't correctness — it's the cost. For every number we rescan almost the whole array: n × n work. At n = 100,000 that's ten billion checks, and the judge stops waiting.
So here's the question that cracks it open: we never used the fact that the array is sorted. What does sorted actually buy us?
Picture two fingers. One on the smallest number (far left), one on the largest (far right). Add them.
- If the sum is too big, which finger can possibly help? Only the right one — every number to its left is smaller. So move it left.
- If the sum is too small, the same logic in reverse: the left finger walks right, toward bigger numbers.
- If it's exactly right — you're done.
Every step, the sum moves in precisely the direction you want, because the array is sorted. And here's the part worth sitting with: when you move a finger past a number, you're not skipping it out of laziness — you've just proven it can't be part of any answer with what's left. You never look back. Two fingers, one walk inward, zero wasted work.
Watch the fingers close in
Step through it. Watch how every move discards a number you've just proven useless:
Look for the move where the sum overshoots the target: hi steps left, and an entire stretch of too-big numbers is gone in a single motion. That's the O(n²) work evaporating in front of you.
The whole thing, in eight lines
Now the code — and every line maps to a finger you just watched move:
The two ifs are the two fingers: sum < target walks lo up toward bigger numbers, sum > target walks hi down toward smaller ones. The while (lo < hi) is just "keep going until they meet." No nested loop anywhere in sight.
What to take away
- When to reach for it: a sorted array and you're hunting for a pair — or any condition the two ends can steer toward (a sum, a difference, the closest pair).
- Complexity: O(n) time, O(1) extra space. If the array isn't sorted, sorting first costs O(n log n) — still miles ahead of O(n²).
- The gotcha: it leans entirely on the array being sorted. On unsorted data you can't reorder, reach for a hash set instead.
- In the wild: this is the seed of a whole family — Container With Most Water, 3Sum, removing duplicates in place, merging two sorted lists. Learn it once, recognize it everywhere.
When an array is sorted, two converging pointers let the sum itself tell you which way to move — turning a nested loop into a single pass that never looks back.
Try it yourself
- Two Sum II — Sorted Array (LeetCode 167). The exact pattern, nothing extra. Write it from memory.
- Valid Palindrome (LeetCode 125). Same two-fingers-from-the-ends idea — but applied to characters instead of a sum. Can you spot why it's the same trick?
- Container With Most Water (LeetCode 11), then 3Sum (LeetCode 15). Here two pointers becomes a building block inside a loop. This is where the pattern grows up.
If the nested loop was your first instinct — good. It was mine too, and it's the right place to start. Two pointers isn't something you memorize; it's something that clicks once, on one problem, and then you start seeing it everywhere. Maybe this was that problem.