Binary Search vs Two Pointers: The One Question That Decides It

- Binary search answers "where is X?" on sorted data: pick a midpoint, discard one half, collapse the search space in O(log n).
- Two pointers answers "do X and Y satisfy a relationship?": start at both ends of a sorted array, march inward, cover all candidates in O(n).
- The single-index vs two-index test decides almost everything: one position wanted = binary search, joint condition on two positions = two pointers.
- Running binary search in a loop (O(n log n)) on pair problems is the sign you needed two pointers (O(n)) instead.
- Binary search on the answer is a third pattern: the answer range is monotonic and you binary search over it, not over an array.
- Converging two pointers requires sorted input; the fast-slow (Floyd's) variant is a different technique that works on unsorted data.
You've got a sorted array and a problem that needs two index variables. Binary search or two pointers? Both use sorted data, both are O(1) space, both make you look like you know what you're doing. One will get you to O(log n). The other will leave you running O(n log n) where O(n) was sitting right there the whole time, patiently waiting.
The question that cuts the confusion: are you hunting for one position, or a relationship between two elements?
That's it. One question. The rest of this guide handles the cases that don't cut cleanly.
Binary Search Kills Half the Problem. Every Single Step.
Binary search answers "where is X?" on sorted data. Every iteration, you pick a midpoint and ask: is the target in the left half or the right half? One half gets discarded. The search space collapses. This is not subtle. This is violence.
The invariant: if the target exists, it lives in [lo, hi]. You maintain that invariant on every iteration until the window shrinks to nothing or you find the answer.
def binary_search(nums: list[int], target: int) -> int: lo, hi = 0, len(nums) - 1 while lo <= hi: mid = lo + (hi - lo) // 2 if nums[mid] == target: return mid elif nums[mid] < target: lo = mid + 1 else: hi = mid - 1 return -1
Traced on actual data:
[1, 3, 5, 7, 9, 11, 13, 15] target=11
lo=0 hi=7
mid=3 (nums[3]=7 < 11, go right)
lo=4 hi=7
mid=5 (nums[5]=11 == 11, found!)
Two comparisons. For a billion elements, you're done in 30 steps. That's not an algorithm. That's a threat. O(log n) time, O(1) space (iterative). Binary search doesn't just find values. It also finds the boundary where a condition flips, which is what makes the invariant framing so powerful for harder variants.
Two Pointers: That Whole Half Is Already Dead
Two pointers (converging variant) answers "do X and Y satisfy some relationship?" Both pointers start at opposite ends of a sorted array and march inward. They never go backward. The whole trick is sorted order turning "I don't need to check those" into a guarantee rather than a guess.
Sorted order lets you reason about what you've already eliminated. If the sum of the leftmost and rightmost elements is too small, the left pointer must advance. There's no pair involving that element that can work. If the sum is too large, the right pointer retreats. You cover the whole array in at most n steps.
def two_sum_sorted(nums: list[int], target: int) -> tuple[int, int]: left, right = 0, len(nums) - 1 while left < right: current_sum = nums[left] + nums[right] if current_sum == target: return (left, right) elif current_sum < target: left += 1 else: right -= 1 return (-1, -1)
Watch the pointers close in:
[1, 4, 6, 8, 11, 15] target=19
L=0 R=5 (sum=16, too small, advance L)
L=1 R=5 (sum=4+15=19, found!)
Two steps. O(n) time, O(1) space. The whole array in at most n steps. The Two Pointer Algorithm guide goes deep on the elimination proof and the fast-slow variant.
The Tradeoff in One Table
| Property | Binary Search | Two Pointers |
|---|---|---|
| Time complexity | O(log n) | O(n) |
| Space complexity | O(1) iterative, O(log n) recursive | O(1) |
| Requires sorted input | Yes | Yes (converging variant) |
| Finds | Single value or boundary position | Pair or range satisfying a relationship |
| Typical use | Search, lower bound, upper bound | Two-sum variants, pair problems, palindrome checks |
Binary search is faster per element touched. Two pointers touches more elements but avoids the n separate binary searches that would otherwise be needed. When two pointers applies, the alternative is O(n log n). Two pointers gets you to O(n). That gap matters. It will be noticed.
Two Questions. Then You Know.
When you see a sorted array and feel the urge to use two index variables but can't remember which pattern applies, run through these two questions before you commit to anything:
Question 1: Am I finding one index, or two?
Single index (where does 11 live? where does sorted order first break?): binary search. Two indices (which pair sums to 19? which two elements have minimum absolute difference?): two pointers.
Question 2: Can a single comparison tell me which half to discard?
Binary search needs to eliminate a full half of the remaining search space on every step. If nums[mid] < target, everything to the left of mid is dead. That clean halving is what buys O(log n). If you can't make that call about a half, you can't do binary search. You can, however, often discard one element's candidates with two pointers.
These two questions handle the vast majority of sorted-array problems. Worked examples do more work than theory for the edge cases, so let's look at those.
Four Problems, Two Techniques
"Find target in sorted array" (LeetCode 704)
Binary search wins cleanly. O(log n) vs O(n) if you scan linearly. Two pointers gives you nothing since you're not looking for a relationship between two elements.
def search(nums: list[int], target: int) -> int: lo, hi = 0, len(nums) - 1 while lo <= hi: mid = lo + (hi - lo) // 2 if nums[mid] == target: return mid elif nums[mid] < target: lo = mid + 1 else: hi = mid - 1 return -1
"Two sum in sorted array" (LeetCode 167)
Two pointers wins cleanly. O(n) vs O(n log n) if you run binary search once per element.
def two_sum(numbers: list[int], target: int) -> list[int]: left, right = 0, len(numbers) - 1 while left < right: current_sum = numbers[left] + numbers[right] if current_sum == target: return [left + 1, right + 1] elif current_sum < target: left += 1 else: right -= 1 return []
The binary search version works (for each element, binary search for its complement) but it's O(n log n). Two pointers is O(n) and cleaner. This is exactly the trap the interviewer is watching for.
"Find pair with minimum absolute difference"
Two pointers, full stop. After sorting, adjacent elements have the smallest possible differences. Walk the array once, track the minimum gap. O(n log n) for the sort, O(n) for the scan.
def min_abs_diff_pair(nums: list[int]) -> tuple[int, int]: nums.sort() min_diff = float('inf') result = (nums[0], nums[1]) for i in range(len(nums) - 1): diff = nums[i + 1] - nums[i] if diff < min_diff: min_diff = diff result = (nums[i], nums[i + 1]) return result
"First element >= target" (lower bound)
Binary search. This is the classic boundary-finding problem. Two pointers gives you no way to discard half the candidates.
def lower_bound(nums: list[int], target: int) -> int: lo, hi = 0, len(nums) while lo < hi: mid = lo + (hi - lo) // 2 if nums[mid] < target: lo = mid + 1 else: hi = mid return lo
Wait, Binary Search Works Without a Sorted Array?
There's a third pattern that looks like binary search but has nothing to do with sorted arrays. It will blindside you the first time you see it in an interview.
Some problems have no sorted array to search, but their answer space is monotonic. "Minimum capacity to ship all packages in D days" (LeetCode 1011) is the canonical example. There's no array you're searching through for a value. But there's a feasibility function: if capacity C works, then C+1 also works. That monotonicity is all binary search needs.
Binary search on the answer uses the feasibility check as the comparison function, and binary searches over the answer range instead of an input array.
def ship_within_days(weights: list[int], days: int) -> int: def can_ship(capacity: int) -> bool: current_load, day_count = 0, 1 for weight in weights: if current_load + weight > capacity: day_count += 1 current_load = 0 current_load += weight return day_count <= days lo, hi = max(weights), sum(weights) while lo < hi: mid = lo + (hi - lo) // 2 if can_ship(mid): hi = mid else: lo = mid + 1 return lo
Two pointers cannot do this. There's no pair relationship. Binary search on the answer is a different mental model entirely, and it's the Binary Search on the Answer guide that walks through it properly.
If you're deciding between two pointers and sliding window instead, the Two Pointers vs Sliding Window guide handles that fork.
The Mistakes That Cost You the Offer
Running binary search n times when two pointers would do. Two-sum type problems invite this: for each element, binary search for its complement. O(n log n). Two pointers does it in O(n). The worst part is that the O(n log n) solution is technically correct. You just left a whole factor of log n on the table and the interviewer saw it happen in real time.
Using two pointers to find a single position. The technique is built around two elements having a joint relationship. Without that, you're just scanning linearly.
Forgetting that converging two pointers needs sorted data. The fast-slow variant (Floyd's cycle detection) works on unsorted data but is a completely different animal. The converging variant works because sorted order makes the elimination proof sound. Run it on unsorted data and you won't find valid pairs. You'll just have a wrong answer and a confident face.
Seeing "sorted array + two indices" as binary search signal. Both techniques use sorted arrays and two indices. The signal is the question: single position, or relationship between two elements.
If you get asked "why not binary search?" in a mock interview and you freeze, that's the gap. Not the algorithm. The articulation. Most candidates can get to a working solution. Fewer can explain the O(n) vs O(n log n) tradeoff on the spot while someone stares at them. Practicing at SpaceComplexity is specifically designed for this: voice-based mock interviews where the interviewer pushes back on technique choice, and you have to say the quiet part out loud.
Further Reading
- Binary search on Wikipedia: formal definition, history, and correctness analysis
- Two pointers technique on GeeksforGeeks: patterns, variants, and worked problems
- LeetCode 704: Binary Search: the canonical binary search problem
- LeetCode 167: Two Sum II: the canonical converging two-pointer problem
- LeetCode 1011: Capacity to Ship Packages: binary search on the answer in its clearest form