AlgoMaster Logo

Introduction to Binary Search

High Priority21 min readUpdated June 4, 2026
Listen to this chapter
Unlock Audio

Looking up a word in a physical dictionary does not require flipping through every page. You open it near the middle, check whether the word comes before or after that page, and repeat in the relevant half. Within seconds, thousands of pages narrow down to one.

This idea of repeatedly halving the search space is the essence of binary search. It appears across computer science, from database indexing to machine learning algorithms.

The concept itself is straightforward. The harder parts are recognizing when to apply it and handling the edge cases correctly. Off-by-one errors are easy to introduce. The templates in this chapter cover the standard variants. Problems like rotated-array search, peak finding, and 2D matrix search build on these but need their own invariants.

What Is Binary Search?

Binary search is a search algorithm that finds the position of a target value within a sorted collection. Instead of checking each element one by one, it compares the target with the middle element and eliminates half of the remaining elements with each comparison.

Loading simulation...

The key requirement is that the search space must have a monotonic property. In the simplest case, this means the array is sorted. But more generally, it means there is some condition that is false for all elements on one side of a boundary and true for all elements on the other side.

Why O(log n)?

With each comparison, binary search eliminates half the remaining elements. If you start with n elements:

  • After 1 comparison: n/2 elements remain
  • After 2 comparisons: n/4 elements remain
  • After k comparisons: n/2^k elements remain

We stop when only 1 element remains: n/2^k = 1, which gives k = log₂(n).

For an array of 1 billion elements, binary search needs at most 30 comparisons. Linear search might need 1 billion.

Loop Invariants and Boundary Conditions

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].

  • Loop condition: lo <= hi. The interval is non-empty as long as lo does not exceed hi.
  • Updates: 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.
  • Termination: when 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.

  • Loop condition: lo < hi. When lo == hi, exactly one candidate position remains.
  • Updates: 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).
  • Termination: when 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.

When to Use Binary Search

Binary search is the right tool when your problem has these characteristics:

1. The search space is sorted or has a monotonic property

The classic case is a sorted array. But the search space could also be a range of possible answers where you can determine if an answer is "too small" or "too big."

2. You can eliminate half the search space with each check

Given any point in the search space, you must be able to determine which half contains the answer. This is what makes binary search work.

3. Random access is available

Binary search needs O(1) random access to be O(log n). On a linked list it degrades to O(n log n) because finding the middle element requires walking from the head.

Common problem types where binary search applies:

Problem TypeExamples
Finding exact valueSearch in sorted array, search in matrix
Finding boundaryFirst/last occurrence, first bad version
Finding peak/valleyFind peak element, find minimum in rotated array
Search on answerKoko eating bananas, capacity to ship packages
OptimizationMinimize maximum, maximize minimum

Binary Search Variants and Templates

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.

Variant 1: Standard Binary Search (Find Exact Target)

This is the classic version. Given a sorted array, find if a target exists and return its index.

Template:

Key Points:

  • Use left <= right because when left == right, we still have one element to check
  • In Java, C++, C#, and Go, use left + (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.
  • Return immediately when target is found

Example Walkthrough:

Variant 2: Find Lower Bound (First Occurrence)

When duplicates exist, standard binary search might return any occurrence. This variant finds the first (leftmost) occurrence of the target.

Template:

Key Points:

  • Use left < right (not <=) because we are looking for a boundary
  • When nums[mid] >= target, we keep mid in the search space (right = mid) because it might be the answer
  • The loop terminates when left == right, pointing to the first element >= target

Example Walkthrough:

Variant 3: Find Upper Bound (Last Occurrence)

This variant finds the last (rightmost) occurrence of the target.

Template:

Key Points:

  • When nums[mid] <= target, we move left to mid + 1 because we want to find something strictly greater
  • The loop finds the first element strictly greater than target
  • The last occurrence is at index left - 1

Example Walkthrough:

Variant 4: Find First Element Greater Than Target

Sometimes you need the first element strictly greater than a given value. This is useful for insertion points and range queries.

Template:

Example Walkthrough:

Variant 5: Find First Element Greater Than or Equal To Target

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.

Variant 6: Binary Search on Answer Space

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:

  • Define the search space as the range of possible answers
  • Write a helper function to check if a candidate answer is feasible
  • If you are minimizing, search for the first feasible value
  • If you are maximizing, search for the last feasible value

Example: Minimum Capacity to Ship Packages

Given packages with weights and a required number of days, find the minimum ship capacity needed.

Binary Search on Answer Space

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:

  • Koko Eating Bananas (LC 875): find the minimum eating speed.
  • Capacity to Ship Packages Within D Days (LC 1011): find the minimum ship capacity.
  • Split Array Largest Sum (LC 410): find the minimum possible largest subarray sum.

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:

  • Lower bound: lo = 1. She must eat at least 1 banana per hour to make progress.
  • Upper bound: 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:

  • The lower bound is the smallest value that could possibly be a valid answer. Often this is 1, the minimum array element, or the maximum array element (when the answer must be at least as big as any single element, as in Split Array Largest Sum).
  • The upper bound is the largest value that is guaranteed to be feasible. Often this is the maximum array element, the sum of all elements, or some natural ceiling from the problem statement.

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.

Choosing the Right Template

Here is a quick reference for choosing the correct variant:

Problem TypeTemplateLoop ConditionResult
Find exact valueVariant 1left <= rightIndex or -1
First occurrenceVariant 2left < rightLeft bound
Last occurrenceVariant 3left < rightRight bound - 1
First > targetVariant 4left < rightLeft (insertion point)
First >= targetVariant 5left < rightLeft (insertion point)
Minimum feasibleVariant 6left < rightMinimum answer
Maximum feasibleVariant 6 (modified)left < rightMaximum answer

Example Walkthrough: Count Elements in Range

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.

Common Mistakes and How to Avoid Them

Mistake 1: Off-by-One Errors in Loop Condition

A frequent source of bugs is mixing up left <= right and left < right.

  • Use left <= right when you want to check every element (Variant 1)
  • Use left < right when you want to converge to a boundary (Variants 2-6)

Mistake 2: Integer Overflow in Mid Calculation

Mistake 3: Infinite Loop from Wrong Update

When using left = mid, use mid = left + (right - left + 1) / 2 (round up) to avoid infinite loops.

Mistake 4: Wrong Initial Bounds

Mistake 5: Not Handling Edge Cases

Always consider:

  • Empty array
  • Single element array
  • Target smaller than all elements
  • Target larger than all elements
  • All elements are the same

Variants Beyond Sorted Arrays

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.

  • Search in Rotated Sorted Array: The array was sorted in ascending order, then rotated at an unknown pivot. At each 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.
  • Find Peak Element: The array is not sorted, but adjacent elements are distinct. A peak is any index 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.
  • Search a 2D Matrix: The matrix is row-sorted and the first element of each row is greater than the last element of the previous row. Treat it as a flat sorted array of length rows * cols. Map a flat index mid to matrix[mid / cols][mid % cols] and apply Variant 1.
  • Floating-point binary search: When the answer is a real number (for example, square root, or allocating a continuous resource), integer comparisons no longer terminate the loop. Replace the condition with 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.

Quiz

Introduction Quiz

10 quizzes