Binary search gets taught as "the sorted-array trick," which undersells it badly. The actual idea is more general and more useful: any time a single comparison can reliably throw away half of everything you still need to check, you can search that space in O(log n) instead of O(n) — whether that space is array indices, a rotated array's structure, or (most powerfully) the space of *possible answers* to a question you haven't directly solved yet.
The invariant that makes it correct
Every binary search in this topic maintains the same shape: a range [lo, hi] that is guaranteed to still contain the answer, shrunk by roughly half on every iteration by discarding whichever side a single comparison proves can't contain it. Get the comparison wrong — or get the boundary update wrong (mid vs mid + 1 vs mid - 1) — and you either loop forever or silently discard the correct answer. This is why binary search has a reputation for being deceptively hard to get exactly right on the first try, even though the idea is simple.
Before coding, write down in words exactly what lo, hi, and the loop condition mean for your specific problem — "the answer is somewhere in [lo, hi] inclusive" is a different invariant than "the answer is somewhere in [lo, hi)," and they need different loop conditions (lo <= hi vs lo < hi) and different boundary updates. Mixing the two shapes mid-implementation is where most off-by-one bugs come from.
Binary search doesn't require a sorted array — it requires a monotonic comparison
Find Peak Element is the clearest proof: the array isn't sorted at all, yet binary search still works, because the comparison nums[mid] < nums[mid + 1] reliably guarantees a peak exists on one particular side. That's the real requirement — not "is this sorted," but "does comparing the middle to something nearby let me provably eliminate half the space."
Binary search on the answer: the pattern that actually shows up in interviews
Koko Eating Bananas and Capacity To Ship Packages Within D Days look nothing like "search a sorted array" at first glance — there's no array to binary search *through*. The insight: the space of *possible answers* (every eating speed from 1 to max(piles), every capacity from max(weights) to sum(weights)) is itself monotonic — if speed k is fast enough to finish in time, every speed faster than k is too. That monotonicity is exactly the property binary search needs, so instead of testing every candidate answer one at a time (slow), you binary search the answer space directly, using a cheap feasibility check as the comparison.
// The shape every "binary search on the answer" problem shares:
long long lo = smallest_possible_answer;
long long hi = largest_possible_answer;
while (lo < hi) {
long long mid = lo + (hi - lo) / 2;
if (feasible(mid)) hi = mid; // mid works -- a smaller answer might too
else lo = mid + 1; // mid doesn't work -- need something bigger
}
// lo is now the SMALLEST value for which feasible() is true.
// feasible() itself is usually O(n) -- the win is doing O(log(range))
// feasibility checks instead of O(range) of them.Common mistakes
- Mixing loop-condition styles —
lo <= hiwithhi = mid - 1/lo = mid + 1is one consistent style;lo < hiwithhi = mid/lo = mid + 1is another. Both are correct on their own — mixing pieces from each is how infinite loops and off-by-ones happen. - Integer overflow in
mid = (lo + hi) / 2— with large bounds,lo + hican overflow before the division happens.mid = lo + (hi - lo) / 2avoids it. Sqrt(x) has a second, sneakier version of this same bug:mid * midoverflows a 32-bitintlong beforexreachesINT_MAX, so the comparison itself needs a 64-bit type even though the inputs and answer both fit inint. - Comparing against the wrong reference point in a rotated array — Find Minimum in Rotated Sorted Array needs
nums[mid]compared againstnums[hi](the right edge), notnums[lo]; getting this backwards silently searches the wrong half. - Turning a correct O(log n) shape back into O(n) accidentally — Find First and Last Position is the classic trap: find one occurrence with binary search, then linearly scan outward to find the edges of the run. That's O(n) again the moment a value repeats a lot. Two separate lower-bound searches keep it O(log n).
Takeaways
- Binary search needs a monotonic comparison that discards half the remaining space — not necessarily a sorted array (Find Peak Element proves this).
- Pick one loop-condition style (inclusive
[lo, hi]or half-open[lo, hi)) and keep every boundary update consistent with it — mixing styles is where off-by-ones come from. - "Binary search on the answer" turns a monotonic feasibility question (can this speed/capacity/value work?) into an O(log(range)) search over the answer space itself, instead of testing every candidate answer one at a time.
- Watch for overflow in both the midpoint calculation and, separately, anything you square or multiply against the midpoint (Sqrt(x)'s
mid * midis the sneaky one).
Try it: the foundation
Binary Search itself — get the invariant and the boundary updates exactly right here, since every other problem in this topic is a variation on this same shape.
Loading starter code…
Try it: binary search without a sorted array
Find Peak Element is the clearest demonstration that sortedness was never actually the requirement — a monotonic comparison was.
Loading starter code…
Try it: binary search on the answer
Koko Eating Bananas is the pattern that shows up constantly in interviews once you know to look for it — the walkthrough above is this exact technique.
Loading starter code…
That's 3 of the 12 problems in this topic. Median of Two Sorted Arrays is worth doing once these three feel automatic — it's the hardest problem in this topic, and the one most worth taking slowly. See the full Binary Search set →
Checkpoint · Binary Search
5 questions · pass at 70%
Finished Binary Search?
Pass the quiz to complete it automatically.