Every elegant technique was born out of frustration with something slow, ugly, and broken. Let's feel that frustration first.
You are handed a sorted array of integers. A number target is given to you. Your job:
📋 Problem — Two Sum (Sorted Input)
Given a sorted array nums and an integer target, return the indices of the two numbers that add up to target.
Input: nums = [-4, -1, 1, 3, 5, 8, 11], target = 9
Output: indices [2, 5] → nums[2] + nums[5] = 1 + 8 = 9 ✓
Stop reading for one second. What's the first solution that comes to your mind?
Most people, including experienced developers on a first pass, reach for this:
def two_sum_brute(nums, target): n = len(nums) for i in range(n): # fix first number for j in range(i + 1, n): # try every other number if nums[i] + nums[j] == target: return [i, j] return []
This is correct. It passes every test case. It will be accepted on LeetCode for small inputs.
It is also, fundamentally, thoughtless.
For an array of size n, the nested loop runs n × (n-1) / 2times. That's O(n²).
A real dataset — say, a list of 100,000 stock prices where you want a pair summing to a target profit — the brute force would compute 5 billion comparisons. At 10⁹ operations/second, that's 5 seconds of waiting. In a live system, that's a timeout. That's a failure.
Look at the sorted array again. Stare at it.
There are three outcomes when you pick any two numbers from a sorted array and sum them:
← left. Every pair to the right is already too big.→ right. Every pair to the left is already too small.Each comparison eliminates an entire direction. You're not checking all possibilities — you're reasoning your way to the answer.
We place one pointer at the far left, one at the far right. They march toward each other, each step informed by what we see.
def two_sum_sorted(nums, target): L, R = 0, len(nums) - 1 # start at opposite ends while L < R: # until pointers meet current = nums[L] + nums[R] if current == target: return [L, R] # found it elif current < target: L += 1 # sum too small → move left up else: R - = 1 # sum too large → move right down return [] # no pair found
Watch it execute on nums = [-4, -1, 1, 3, 5, 8, 11], target = 9:
L=0, R=6 → -4 + 11 = 7. Too small. Move L right.L=1, R=6 → -1 + 11 = 10. Too big. Move R left.L=1, R=5 → -1 + 8 = 7. Too small. Move L right.L=2, R=5 → 1 + 8 = 9. ✓ Target found! Return [2, 5].4 comparisons instead of 21. The array had 7 elements — brute force would check C(7,2) = 21 pairs. Two pointers found the answer in 4.
Sorted arrays, or data with a monotonic property — if you can reason "moving this pointer increases/decreases the value," you can use two pointers.
At every step, the answer (if it exists) is always between L and R. Neither pointer ever skips past it. This is the proof of correctness.
L and R together make at most n moves total. Every iteration moves at least one pointer. The loop terminates in ≤ n steps.
Same-direction (fast/slow), opposite-direction (squeeze), multiple arrays, sliding window — all cousins of the same core idea.