AI SWE Prep
0/23in Data Structures & Algorithms
Data Structures & Algorithms

Binary Search & the Answer Space

Halve the search space every step. Then learn to binary-search on answers, not just arrays.

~16 minLesson 18 of 60
Your progressNot started

Binary search is the purest example of the “halve the problem” idea. Given a sorted array, you find a value by repeatedly checking the middle and throwing away the half that cannot contain your target. Each comparison cuts the search space in two, so searching a million elements takes about 20 steps, not a million.

Binary search · target 23Step 1 / 4
02
15
28
312
416
523
638
745
856
972
lo
hi
Searching for 23. The whole array is in play.
midpoint being testedactive range [lo, hi]found

Step through it and watch the shaded range collapse. At every step you look at the midpoint and make a decision: if the midpoint is too small, the answer must be to its right, so discard everything up to and including mid. If it’s too big, discard everything from mid rightward. You never look at the discarded half again — that’s where the log n comes from.

def binary_search(arr, target):
    lo, hi = 0, len(arr) - 1
    while lo <= hi:
        mid = (lo + hi) // 2
        if arr[mid] == target:
            return mid
        if arr[mid] < target:
            lo = mid + 1      # answer is in the right half
        else:
            hi = mid - 1      # answer is in the left half
    return -1                 # not found

The details that trip people up

Binary search is famous for being easy to describe and hard to write correctly. Three things cause almost every bug:

  1. Loop condition. lo <= hi (not <) so that a single-element range is still examined.
  2. Midpoint. In languages with fixed-width integers, (lo + hi) / 2 can overflow; lo + (hi - lo) / 2 is the safe form. (Python’s big ints make this moot, but interviewers still look for it.)
  3. Shrinking correctly. Always move past mid (mid + 1 / mid - 1) so the range strictly shrinks. Forgetting this is the classic infinite loop.

Binary search on the answer, not the array

The deeper, senior-level insight: binary search doesn’t need an array at all. It needs a monotonic predicate — a yes/no question whose answer flips exactly once as you increase some value. Then you can binary-search for that flip point.

“What is the smallest ship capacity that lets me deliver all packages within D days?” As capacity increases, “can I finish in time?” goes from no to yes and never back. That monotonicity means you can binary-search the capacity — even though there’s no sorted array in sight.

This reframing — binary search on the answer space — turns a whole class of optimization problems (“minimize the maximum,” “find the smallest feasible X”) into O(n log range) solutions. Spotting the hidden monotonic predicate is the skill that separates a rote binary search from a genuinely strong answer.

When to reach for it

Reach for binary search when the data is sorted, or when you can phrase the problem as “find the boundary where a condition flips.” If you can define a feasible(x) check that is monotonic, you can almost always binary-search x.

Pattern recognition

Variations

Worked problems

The code changes less than the question. Spend your planning time defining the predicate and the boundary you want.

LeetCode 704Easy
  • Binary search
  • Sorted array

Binary Search

Given a sorted array of distinct integers and a target, return the target’s index or -1 if it is absent.

Approach. Keep an inclusive search window [lo, hi]. Compare the middle to the target and discard the half that cannot contain it. Move past mid so the window strictly shrinks.

Show solution
def search(nums, target):
    lo, hi = 0, len(nums) - 1
    while lo <= hi:
        mid = lo + (hi - lo) // 2
        if nums[mid] == target:
            return mid
        if nums[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    return -1

Complexity. O(log n) time and O(1) space. Each comparison removes half of the remaining window.

LeetCode 34Medium
  • Binary search
  • Bounds

Find First and Last Position

Given a sorted array that may contain duplicates, return the first and last index where target appears. If it does not appear, return [-1, -1].

Approach. Write one helper: lower bound, the first index whose value is at least x. The first target is lower_bound(target). The first value greater than target is lower_bound(target + 1), so the last target is one position before that.

Show solution
def search_range(nums, target):
    def lower_bound(x):
        lo, hi = 0, len(nums)
        while lo < hi:
            mid = lo + (hi - lo) // 2
            if nums[mid] < x:
                lo = mid + 1
            else:
                hi = mid
        return lo

    left = lower_bound(target)
    if left == len(nums) or nums[left] != target:
        return [-1, -1]

    right = lower_bound(target + 1) - 1
    return [left, right]

Complexity. O(log n) time and O(1) space. Two boundary searches replace a linear expansion from a found index.

LeetCode 875Medium
  • Binary search
  • Answer space

Koko Eating Bananas

Given pile sizes and a deadline in hours, find the smallest integer eating speed that lets all piles be finished in time. In one hour, only one pile can be eaten from, and partial piles round up to a full hour.

Approach. Speed is the answer space. If speed k works, any faster speed also works, so can_finish(k) is monotonic. Search the smallest working speed between 1 and the largest pile.

Show solution
def min_eating_speed(piles, h):
    def can_finish(speed):
        hours = 0
        for pile in piles:
            hours += (pile + speed - 1) // speed
        return hours <= h

    lo, hi = 1, max(piles)
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if can_finish(mid):
            hi = mid
        else:
            lo = mid + 1
    return lo

Complexity. O(n log m) time and O(1) space, where m is the largest pile. Each feasibility check scans the piles once.

Your progressNot started