The introduction to binary search covered the standard version: find a target in a sorted array, or return -1. That version returns an index where the target appears. Many problems need something more specific, such as the first or last occurrence of a value, the position where a value should be inserted, or the smallest answer that satisfies a condition.
This chapter covers those variations. Each one keeps the same core idea, cutting the search space in half with every comparison, but changes the loop condition, the pointer updates, or what gets returned. The templates below cover the standard variants. Problems like rotated-array search, peak finding, and 2D matrix search build on these but need their own invariants.
The <= vs < choice in the loop condition is not arbitrary. It follows from the interval convention you pick. Two canonical conventions cover every variant in this chapter.
1. Closed-interval search [lo, hi]
The interval includes both endpoints. The invariant is: the target, if present, lies in [lo, hi].
lo <= hi. The interval is non-empty as long as lo does not exceed hi.lo = mid + 1 or hi = mid - 1. The value at mid has been compared to the target and ruled out, so it is excluded from both sides.lo > hi, the interval is empty and the target is not present.This is the convention used in Variant 1, where every element is checked exactly once before the loop ends.
2. Half-open interval [lo, hi)
The interval includes lo but excludes hi. The invariant is: the first index where the predicate becomes true lies in [lo, hi]. Here hi is treated as a sentinel meaning "past the end of the candidates," so it is a valid answer position even though it is not a valid array index when it equals nums.length.
lo < hi. When lo == hi, exactly one candidate position remains.lo = mid + 1 (predicate is false at mid, so mid is eliminated) or hi = mid (predicate is true at mid, so mid remains a candidate).lo == hi, that single index is the answer.This is the convention used in Variants 2 through 6, where the goal is to find a boundary rather than an exact element.
Why the +1 in lo = mid + 1 matters. The predicate has already been evaluated at mid. If the predicate is false at mid, then mid cannot be the answer, so the search continues at mid + 1. Without the +1, the loop can stall when hi - lo == 1: at that point mid == lo, and setting lo = mid leaves the interval unchanged, producing an infinite loop.
The upper-bisect rounding rule. When the update is lo = mid instead of lo = mid + 1 (this happens when searching for the rightmost index where a predicate is true), compute mid as lo + (hi - lo + 1) / 2 to round up. With the standard lo + (hi - lo) / 2 formula and hi - lo == 1, mid == lo, so lo = mid again leaves the interval unchanged. Rounding up forces mid == hi, which makes progress.
Binary search is not limited to finding a value in a sorted array. These are the common problem types where it applies:
| Problem Type | Examples |
|---|---|
| Finding exact value | Search in sorted array, search in matrix |
| Finding boundary | First/last occurrence, first bad version |
| Finding peak/valley | Find peak element, find minimum in rotated array |
| Search on answer | Koko eating bananas, capacity to ship packages |
| Optimization | Minimize maximum, maximize minimum |
Binary search has several variants. The core idea is the same, but small changes in the template let you solve different problems. Each variant below comes with a reusable template.
This is the classic version. Given a sorted array, find if a target exists and return its index.
Template:
Key Points:
left <= right because when left == right, we still have one element to checkleft + (right - left) / 2 to avoid integer overflow when left + right could exceed the integer type's max value. Python and JavaScript don't have this risk for realistic array sizes, but the form is often kept for consistency.Example Walkthrough:
When duplicates exist, standard binary search might return any occurrence. This variant finds the first (leftmost) occurrence of the target.
Template:
Key Points:
left < right (not <=) because we are looking for a boundarynums[mid] >= target, we keep mid in the search space (right = mid) because it might be the answerleft == right, pointing to the first element >= targetExample Walkthrough:
This variant finds the last (rightmost) occurrence of the target.
Template:
Key Points:
nums[mid] <= target, we move left to mid + 1 because we want to find something strictly greaterleft - 1Example Walkthrough:
Sometimes you need the first element strictly greater than a given value. This is useful for insertion points and range queries.
Template:
Example Walkthrough:
This is equivalent to finding the insertion point if you were to insert the target while maintaining sorted order.
Template:
The loop structure is the same as the find first occurrence template, but the return contract differs: Variant 2 returns -1 when the target is not found, while Variant 5 returns the insertion index.
Instead of searching in an array, this variant searches a range of possible answers. It relies on the property that if a certain value works as an answer, then all values more lenient than it also work.
Template:
Key Points:
Example: Minimum Capacity to Ship Packages
Given packages with weights and a required number of days, find the minimum ship capacity needed.
Variant 6 above shows the template, but the pattern deserves a closer look because it is the most common way binary search appears in mid-to-hard interview problems. The setup is different from searching in an array. Instead of asking "where is element X," the problem asks "what is the smallest or largest value such that some feasibility condition holds." The search space is the range of possible answers, not an array.
Classic examples of this pattern:
The pattern applies when the feasibility predicate is monotonic. If value v is feasible, then every value larger than v is also feasible (or every value smaller, depending on direction). That monotonicity is what makes binary search valid: the feasible values form a contiguous suffix (or prefix) of the candidate range, and you are looking for the boundary.
Template:
Worked example: Koko Eating Bananas.
Koko has piles of bananas, given as an array piles. She picks an eating speed k bananas per hour. Each hour she eats from one pile. If a pile has fewer than k bananas left, she finishes it and moves on; she does not start another pile in the same hour. Given H hours, find the minimum k such that she can finish all piles within H hours.
The feasibility predicate is canFinish(k): returns true if eating at rate k lets her finish in H hours or fewer. The time to finish one pile of size p at rate k is ceil(p / k). The total time is the sum across all piles.
Search bounds:
lo = 1. She must eat at least 1 banana per hour to make progress.hi = max(piles). At this rate she finishes any pile in one hour, so total time is at most piles.length hours. Since the problem guarantees H >= piles.length, max(piles) is always feasible.Monotonicity: if k works, any k' > k also works. Eating faster never takes longer.
Picking the search bounds. This is the part that trips people up most. Two rules cover most problems:
Loose bounds are fine. Binary search is logarithmic, so even a search range of 10^9 finishes in about 30 iterations. The bounds just need to bracket the true answer.
Here is a quick reference for choosing the correct variant:
| Problem Type | Template | Loop Condition | Result |
|---|---|---|---|
| Find exact value | Variant 1 | left <= right | Index or -1 |
| First occurrence | Variant 2 | left < right | Left bound |
| Last occurrence | Variant 3 | left < right | Right bound - 1 |
| First > target | Variant 4 | left < right | Left (insertion point) |
| First >= target | Variant 5 | left < right | Left (insertion point) |
| Minimum feasible | Variant 6 | left < right | Minimum answer |
| Maximum feasible | Variant 6 (modified) | left < right | Maximum answer |
Here is a problem that combines multiple variants. Given a sorted array with duplicates, count how many elements fall within a range [lo, hi].
Problem: Given nums = [1, 2, 2, 3, 3, 3, 4, 5], count elements in range [2, 3].
Solution: Find the first element >= 2 and the first element > 3. The count is the difference of their indices.
A frequent source of bugs is mixing up left <= right and left < right.
left <= right when you want to check every element (Variant 1)left < right when you want to converge to a boundary (Variants 2-6)When using left = mid, use mid = left + (right - left + 1) / 2 (round up) to avoid infinite loops.
Always consider:
The templates above cover sorted arrays and answer-space search. Several common interview problems apply binary search to inputs that are not strictly sorted, but still have enough structure to halve the search space. Each of these problems has its own invariant; the templates above need small modifications.
mid, compare nums[lo] to nums[mid] (or nums[mid] to nums[hi]) to determine which half of the array is still sorted. Then check if the target falls within the sorted half's value range. If yes, recurse on the sorted half; otherwise recurse on the other half.i where nums[i] is greater than both neighbors. Binary search by comparing nums[mid] to nums[mid + 1]. If nums[mid] > nums[mid + 1], a peak lies in [lo, mid] because the sequence is descending at mid. Otherwise a peak lies in [mid + 1, hi] because the sequence is ascending.rows * cols. Map a flat index mid to matrix[mid / cols][mid % cols] and apply Variant 1.hi - lo > epsilon for some small epsilon like 1e-7, and stop once the interval is small enough. The update rules become hi = mid and lo = mid instead of mid - 1 and mid + 1, since there is no "next" floating-point value to skip to.