Deep Dive  ·  Algorithms

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.

15 min read · Interactive examples · Covers LC 33, 153, 162, 875 & more

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:

The boundary pattern — click "Step" to watch binary search converge
Ready — press Step to begin.

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":

Interactive binary search — sorted array
Target:
Press Step to begin searching.
Out of range
Active range
Mid (current guess)
Found!

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.

1

Classic Search

Find an exact value in a sorted array. The only pattern where nums[mid] == target is a meaningful check.

LC 704, 35, 34
2

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, 875
3

Last True

Find the rightmost index where a condition still holds. Used in "maximize the minimum" and upper-bound problems.

LC 1552, 1011 (variant)
4

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, 410
5

Peak / Slope

The array isn't sorted, but has a directional property. Compare adjacent elements to determine which half contains a peak.

LC 162, 852, 1095

Recognition 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:

Array: [4, 5, 6, 7, 0, 1, 2]
4
5
6
7
0
1
2
condition: nums[i] <= nums[last]  (last = 2)
Boolean result:
F
F
F
F
T*
T
T
The minimum element is the first place where an element is <= the last element. The "upper half" (4,5,6,7) is all larger than the last element. The "lower half" (0,1,2) is all smaller. The first T — index 4, value 0 — is our answer.

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.
piles = [3, 6, 7, 11], h = 8  ·  Search space: speed 1..11
1
2
3
4
5
6
7
8
9
10
11
condition: canEatAll(speed) in <= 8 hours
Boolean result (F = can't finish, T = finishes in time):
F
F
F
T*
T
T
T
T
T
T
T
At speed 4: ceil(3/4)+ceil(6/4)+ceil(7/4)+ceil(11/4) = 1+2+2+3 = 8 hours. Exactly fits! Speeds 1–3 take too long. The answer (minimum working speed) is the first T.
Versions 1–8, first bad version = 5
1
2
3
4
5
6
7
8
condition: isBad(version)
Boolean result:
F
F
F
F
T*
T
T
T
Once a build is bad, all subsequent builds are bad (they inherit the bug). This is the clearest possible monotonic property. First True = first bad version. One API call is needed per check, so binary search's O(log n) is a huge win over linear O(n).
Array: [1, 3, 5, 7, 6, 4, 2]
1
3
5
7
6
4
2
condition: arr[i] > arr[i+1]  (or i == last)
Boolean result:
F
F
F
T*
T
T
T
The peak is the first place where the array starts descending. Before the peak, every element is smaller than the next (ascending = false). At the peak and beyond, every element is larger than the next (descending = true). The edge case: the last element has no next element, so we treat it as true (imagine arr[n] = −∞).

The three-question test

When you read a problem, run through this mentally:

1
Does it ask for minimum/maximum/first/last?

Keywords like "minimum X such that", "first occurrence", "smallest valid", "earliest position" are all signals. If yes, continue.

2
Can I test a candidate answer?

Given a value X, can I write a function that returns true/false in polynomial time? If yes, binary search is possible.

3
Does the condition flip only once?

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 ↗Classicnums[mid] == target
LC 35 — Search Insert Position ↗First Truenums[mid] >= target
LC 34 — First & Last Position ↗First True + Last Truenums[mid] == target (run twice)
LC 278 — First Bad Version ↗First TrueisBad(mid)
LC 153 — Min Rotated Array ↗First Truenums[mid] <= nums[last]
LC 154 — Min Rotated Array II ↗First Truesame + handle duplicates
LC 33 — Search Rotated Array ↗Classic variantsorted-half detection
LC 162 — Find Peak Element ↗Peaknums[mid] > nums[mid+1]
LC 852 — Peak in Mountain Array ↗Peakarr[mid] > arr[mid+1]
LC 875 — Koko Eating Bananas ↗Search AnswercanEat(speed, h)
LC 1011 — Ship Packages in D Days ↗Search AnswercanShip(capacity, days)
LC 410 — Split Array Largest Sum ↗Search AnswercanSplit(maxSum, m)
Custom — Min Time with K Workers ↗Search AnswercanFinish(T) with greedy contiguous blocks
LC 1482 — Min Days to Bloom ↗Search AnswercanBloom(day, m, k)
LC 1552 — Magnetic Force Balls ↗Last TruecanPlace(minDist, m)
LC 2187 — Min Time Complete Trips ↗Search AnswertripsIn(time) >= total
LC 69 — Sqrt(x) ↗Last Truemid * mid <= x
LC 74 — Search 2D Matrix ↗Classicmap 1D index to 2D cell
LC 540 — Single Element Sorted Array ↗First Trueparity 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:

1
LC 704 — Binary Search

Core mechanics. Mid calculation, shrinking search space, pointer updates. Do this even if you think you know it — muscle memory matters.

2
LC 278 — First Bad Version

Your first First True problem. The F→T mapping is obvious (good → bad), which makes the template click clearly.

3
LC 153 — Find Minimum in Rotated Array

First True on a non-obvious condition. Practice converting the array to a boolean pattern using nums[mid] <= nums[last].

4
LC 33 — Search in Rotated Array

The classic variant. Uses the sorted-half invariant rather than First True. Tests whether you understand the rotation structure, not just the template.

5
LC 162 — Find Peak Element

Peak/Slope pattern. No sorted structure, but slope direction is enough. Solidifies the idea that binary search doesn't need a fully sorted input.

6
LC 875 — Koko Eating Bananas

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.