latency.lab
Algorithms · Topic 05

Binary Search

Level: foundationTool: halving the search space, not just "sorted arrays"Time: ~60 min

By the end you can

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.

The habit to build

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.

1
3
5
7
9
11
13
▲lo
▲mid
▲hi
Searching for 9 in [1, 3, 5, 7, 9, 11, 13]. mid = 7 < 9, so the entire left half (faded, including mid itself) is eliminated in one comparison — lo jumps to mid + 1, and the search continues in a range half the size.

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.

binary-search-on-the-answer.cpp
// 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 <= hi with hi = mid - 1/lo = mid + 1 is one consistent style; lo < hi with hi = mid/lo = mid + 1 is 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 + hi can overflow before the division happens. mid = lo + (hi - lo) / 2 avoids it. Sqrt(x) has a second, sneakier version of this same bug: mid * mid overflows a 32-bit int long before x reaches INT_MAX, so the comparison itself needs a 64-bit type even though the inputs and answer both fit in int.
  • Comparing against the wrong reference point in a rotated array — Find Minimum in Rotated Sorted Array needs nums[mid] compared against nums[hi] (the right edge), not nums[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 * mid is 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.

Live

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.

Live

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.

Live

Loading starter code…

Keep going

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%

Q01
Find Peak Element works correctly on an array that isn't sorted at all. What does that prove about what binary search actually requires?
WhyComparing nums[mid] to nums[mid+1] guarantees a peak exists on a specific side even without any global ordering. That's the real requirement for binary search: a monotonic, half-eliminating comparison — not sortedness itself.
Q02
Koko Eating Bananas doesn't have an array to search through in the usual sense. What is binary search actually searching over in this problem?
WhyThis is "binary search on the answer": speed is monotonic (if k works, everything faster than k also works), so instead of testing every candidate speed one at a time, you binary search the speed itself using an O(n) feasibility check as the comparison.
Q03
Why does mid = (lo + hi) / 2 risk a bug that mid = lo + (hi - lo) / 2 avoids?
WhyWith large enough lo and hi, their sum can exceed the integer type's range even though both values individually fit fine and the true midpoint would fit too. Computing (hi - lo) first and adding it to lo avoids ever forming that oversized intermediate sum.
Q04
What's the actual bug in finding one occurrence of a target with binary search, then scanning left and right to find the first/last index of a repeated value?
WhyIf the target occupies a large fraction of the array, scanning outward from one found occurrence degrades to O(n). Running a second, independent binary search (for the first index >= target+1, minus one) keeps the whole algorithm at O(log n) regardless of how many times the target repeats.
Q05
Find Minimum in Rotated Sorted Array compares nums[mid] against nums[hi] (the right edge) rather than nums[lo]. Why does that specific comparison matter?
WhyIf nums[mid] > nums[hi], the rotation point (the minimum) must be strictly to the right of mid, because a rotated sorted array's right portion is where the values drop back down. Comparing against nums[lo] instead doesn't cleanly distinguish which side the rotation point is on and leads to searching the wrong half.

Finished Binary Search?

Pass the quiz to complete it automatically.