Binary search is not about finding numbers
Most tutorials teach it wrong. Binary search is actually a boundary-finding algorithm — and once you see that, a huge class of hard problems becomes obvious.
Searching for a boundary, not a value
Every intro course teaches binary search the same way: sorted array, target number, halve the range until you find it. Clean, simple, and — crucially — incomplete.
That framing handles maybe 20% of binary search problems. The other 80% involve no target number at all. They involve rotated arrays, mountain peaks, minimum shipping capacities, eating speeds. And engineers fail them not because they don't know the template, but because they haven't understood what binary search is really doing.
Binary search works whenever a condition transitions from false to true (or true to false) exactly once across a range. Your job is to find that transition point.
Imagine a hidden boolean array. Binary search doesn't care that it's "hidden" — it just halves the search space on each step until it pinpoints the boundary:
F = false (condition not met) · T = true (condition met) · The gold outline shows the current mid
That's the whole game. The condition could be anything — "is this element less than the last element?", "can we ship all packages with this capacity?", "are we on the descending slope?" — but the shape of the answer is always that same row of F's followed by T's.
Try the classic version first
Before we get abstract, here's an interactive classic binary search. Pick a target and watch it find the boundary between "too small" and "found it":
Every binary search problem fits one of five shapes
Once you can identify which shape a problem is, you know which template to reach for. The condition differs — the structure doesn't.
Classic Search
Find an exact value in a sorted array. The only pattern where nums[mid] == target is a meaningful check.
First True
Find the leftmost index where a condition flips from false to true. The workhorse pattern. Most "find minimum" problems are this.
LC 278, 153, 875Last True
Find the rightmost index where a condition still holds. Used in "maximize the minimum" and upper-bound problems.
LC 1552, 1011 (variant)Binary Search on Answer
The search space isn't an array — it's a range of possible answers. You ask "does this value work?" and binary search the feasibility.
LC 875, 1011, 410Peak / Slope
The array isn't sorted, but has a directional property. Compare adjacent elements to determine which half contains a peak.
LC 162, 852, 1095Recognition shortcut: If a problem asks for minimum X such that a condition works, or first occurrence of something, it's almost certainly Pattern 2 — First True. This covers Koko Bananas, Ship Packages, First Bad Version, and Rotated Array Minimum all at once.
The templates
Two templates handle patterns 2–4. Pick one and stick to it — mixing them causes infinite loops and off-by-one bugs.
// ── FIRST TRUE (AlgoMonster style) ────────────────────────── int left = 0, right = n - 1, boundary = -1; while (left <= right) { int mid = left + (right - left) / 2; if (condition(mid)) { boundary = mid; // save — this might be the answer right = mid - 1; // but search left for an earlier one } else { left = mid + 1; // definitely not here, go right } } return boundary; // ── LAST TRUE ──────────────────────────────────────────────── int left = 0, right = n - 1, boundary = -1; while (left <= right) { int mid = left + (right - left) / 2; if (condition(mid)) { boundary = mid; // save — this might be the answer left = mid + 1; // but search right for a later one } else { right = mid - 1; } } return boundary;
The three most common mistakes: (1) Infinite loop — forgetting that mid must always be excluded from one side. (2) Losing the boundary — not storing mid when the condition is true before searching further. (3) Mixing templates — using while(left < right) with right = mid - 1 pointer updates that assume while(left <= right). Pick one template per problem and stay consistent.
The skill that unlocks every problem
Converting a problem into an F/T boolean array is the hardest skill to teach and the most valuable to have. Once you can look at a problem and immediately visualise the underlying boolean pattern, the algorithm becomes mechanical.
Here's the conversion for four classic problems. Notice the pattern is always the same — only the condition changes:
Edge case: if the array isn't rotated (e.g. [1,2,3,4,5]), all elements are <= 5, so the result is T T T T T and index 0 is returned correctly.
The three-question test
When you read a problem, run through this mentally:
Keywords like "minimum X such that", "first occurrence", "smallest valid", "earliest position" are all signals. If yes, continue.
Given a value X, can I write a function that returns true/false in polynomial time? If yes, binary search is possible.
If the condition is true for X, is it guaranteed true for all X+1, X+2, ...? (or false for all smaller values?) This is monotonicity — the requirement for binary search to be correct.
Interview sentence that signals expertise: "The condition transitions from false to true monotonically across the search space, so I can binary search for the first true boundary in O(log n)." Interviewers recognize this framing immediately.
Search in Rotated Sorted Array — the variant everyone struggles with
LC 153 (find minimum in rotated array) maps cleanly to First True. LC 33 (search for a target in a rotated array) is slightly different — you're not finding a boundary, you're exploiting the invariant that one half is always sorted.
At any mid, either the left half or the right half is guaranteed sorted. You check which half is sorted, then check whether the target lives inside that sorted half. If yes, go there. If no, go to the other half.
int left = 0, right = nums.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] == target) return mid; // Determine which half is sorted if (nums[left] <= nums[mid]) { // Left half is sorted if (target >= nums[left] && target < nums[mid]) right = mid - 1; // target is in left half else left = mid + 1; // target must be in right half } else { // Right half is sorted if (target > nums[mid] && target <= nums[right]) left = mid + 1; // target is in right half else right = mid - 1; // target must be in left half } } return -1;
The mental model: think of the rotated array as two sorted segments stuck together. At each step you figure out which segment your mid is in, and whether the target is inside the sorted range you can see. You discard the half that can't contain the target.
LC 33 vs LC 153: LC 153 is "find the boundary" (First True pattern). LC 33 is "search using the sorted-half invariant". The rotation structure is the same — the strategy differs because the goal differs.
The pattern map — 25 problems, 5 patterns
Every binary search problem on LeetCode fits one of the five patterns. Here's the full map. Sort by pattern, find the problem, know the approach.
| Problem | Pattern | Condition |
|---|---|---|
| LC 704 — Binary Search ↗ | Classic | nums[mid] == target |
| LC 35 — Search Insert Position ↗ | First True | nums[mid] >= target |
| LC 34 — First & Last Position ↗ | First True + Last True | nums[mid] == target (run twice) |
| LC 278 — First Bad Version ↗ | First True | isBad(mid) |
| LC 153 — Min Rotated Array ↗ | First True | nums[mid] <= nums[last] |
| LC 154 — Min Rotated Array II ↗ | First True | same + handle duplicates |
| LC 33 — Search Rotated Array ↗ | Classic variant | sorted-half detection |
| LC 162 — Find Peak Element ↗ | Peak | nums[mid] > nums[mid+1] |
| LC 852 — Peak in Mountain Array ↗ | Peak | arr[mid] > arr[mid+1] |
| LC 875 — Koko Eating Bananas ↗ | Search Answer | canEat(speed, h) |
| LC 1011 — Ship Packages in D Days ↗ | Search Answer | canShip(capacity, days) |
| LC 410 — Split Array Largest Sum ↗ | Search Answer | canSplit(maxSum, m) |
| Custom — Min Time with K Workers ↗ | Search Answer | canFinish(T) with greedy contiguous blocks |
| LC 1482 — Min Days to Bloom ↗ | Search Answer | canBloom(day, m, k) |
| LC 1552 — Magnetic Force Balls ↗ | Last True | canPlace(minDist, m) |
| LC 2187 — Min Time Complete Trips ↗ | Search Answer | tripsIn(time) >= total |
| LC 69 — Sqrt(x) ↗ | Last True | mid * mid <= x |
| LC 74 — Search 2D Matrix ↗ | Classic | map 1D index to 2D cell |
| LC 540 — Single Element Sorted Array ↗ | First True | parity of index after mid |
The 6-problem learning path
If you want to master binary search in the shortest time, do these six problems in order. They cover every pattern and build on each other:
Core mechanics. Mid calculation, shrinking search space, pointer updates. Do this even if you think you know it — muscle memory matters.
Your first First True problem. The F→T mapping is obvious (good → bad), which makes the template click clearly.
First True on a non-obvious condition. Practice converting the array to a boolean pattern using nums[mid] <= nums[last].
The classic variant. Uses the sorted-half invariant rather than First True. Tests whether you understand the rotation structure, not just the template.
Peak/Slope pattern. No sorted structure, but slope direction is enough. Solidifies the idea that binary search doesn't need a fully sorted input.
Binary Search on Answer. The search space is "possible speeds", not an array. Once this clicks, LC 1011, 410, and 1482 all feel identical.
One sentence that covers everything
Binary search works whenever you can test a candidate answer in polynomial time and the condition is monotonic — false before some threshold, true after it.
The number-in-sorted-array case is just the simplest version of this. Once you've internalised the boundary-finding frame, you'll start seeing binary search opportunities everywhere — in problems that never mention arrays at all.
The engineers who solve these problems in interviews in under ten minutes are not smarter. They've just seen the F→T pattern enough times that converting a new problem takes seconds, not minutes. Build that pattern recognition through deliberate practice on the six problems above, and the rest follows.