Binary Search
Find elements or boundaries in sorted data in O(log n) time by repeatedly halving the search space. Also applies to "binary search on the answer space" for optimization problems.
Learn & Reference
Understanding the Pattern
Binary Search Pattern
Binary search is one of the most fundamental algorithms in computer science. At its core, it exploits sorted order (or a monotonic property) to eliminate half the search space in each step, achieving O(log n) time instead of O(n).
Core Concept
Given a sorted array, instead of scanning every element, pick the middle element and compare:
- If it matches the target, you're done
- If the target is smaller, search the left half
- If the target is larger, search the right half
Each comparison eliminates half the remaining elements. For an array of 1,000,000 elements, you need at most 20 comparisons (log₂(1,000,000) ≈ 20).
The 3 Binary Search Archetypes
1. Standard Element Search (lo <= hi)
Find a specific target in a sorted array. Returns its index or -1 if not found.
Template:
lo, hi = 0, n - 1
while lo <= hi:
mid = lo + (hi - lo) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
lo = mid + 1
else:
hi = mid - 1
return -1
Key: lo <= hi ensures every element is checked. Both lo and hi move past mid.
2. Boundary Finding / Bisect (lo < hi)
Find the leftmost or rightmost position satisfying a condition. Used for "first true" or "last true" in a boolean sequence.
Template (find leftmost true):
lo, hi = 0, n - 1
while lo < hi:
mid = lo + (hi - lo) // 2
if condition(mid):
hi = mid # mid could be the answer
else:
lo = mid + 1 # mid is not the answer
return lo
Key: lo < hi terminates when they converge. One pointer moves to mid, the other past it.
3. Binary Search on the Answer Space
Instead of searching an array, search over a range of possible answers. Used for optimization problems where you can check "is answer X feasible?"
Template:
lo, hi = min_possible, max_possible
while lo < hi:
mid = lo + (hi - lo) // 2
if is_feasible(mid):
hi = mid # try smaller answer
else:
lo = mid + 1 # need larger answer
return lo
Key: The feasibility function must be monotonic — once feasible at some value, it stays feasible for all larger (or smaller) values.
When Binary Search Fails
- The data is not sorted and has no monotonic property
- You need to find all occurrences, not just one boundary
- The "feasibility" function is not monotonic (feasible → infeasible → feasible)
- The search space cannot be halved by a single comparison
Common Mistakes
- Integer overflow in mid calculation:
mid = (lo + hi) / 2overflows for large values. Always usemid = lo + (hi - lo) / 2 - Wrong loop condition: Using
lo < hifor standard search misses the last element. Usinglo <= hifor boundary search causes infinite loops - Off-by-one in pointer updates: For
lo <= hi, both pointers must move past mid (lo = mid + 1,hi = mid - 1). Forlo < hi, exactly one pointer stays at mid - Infinite loop from wrong mid update: If
hi = midandlo = midare both possible, the loop never terminates. One side must always move past mid - Comparing to wrong reference in rotated arrays: In rotated array problems, comparing
nums[mid]tonums[lo]vsnums[hi]gives different results — choose carefully
When to Use
Template
1# --- Standard Binary Search (find target) ---2def binary_search(nums, target):3lo, hi = 0, len(nums) - 14while lo <= hi:5mid = lo + (hi - lo) // 26if nums[mid] == target:7return mid8elif nums[mid] < target:9lo = mid + 110else:11hi = mid - 112return -1 # not found1314# --- Boundary Search (find first position where condition is true) ---15def find_boundary(nums):16lo, hi = 0, len(nums) - 117while lo < hi:18mid = lo + (hi - lo) // 219if condition(nums[mid]):20hi = mid # mid might be the answer21else:22lo = mid + 1 # mid is not the answer23return lo2425# --- Binary Search on Answer Space ---26def min_answer(lo, hi):27while lo < hi:28mid = lo + (hi - lo) // 229if is_feasible(mid):30hi = mid31else:32lo = mid + 133return lo
Syntax Reference
1# --- bisect module (binary search utilities) ---2import bisect34# bisect_left: index where target would be inserted (leftmost position)5i = bisect.bisect_left(arr, target) # arr must be sorted6# bisect_right: index after the last occurrence of target7j = bisect.bisect_right(arr, target)89# insort: insert while maintaining sorted order10bisect.insort_left(arr, val) # O(n) due to shifting11bisect.insort_right(arr, val)1213# Check if target exists14def binary_search(arr, target):15i = bisect.bisect_left(arr, target)16return i < len(arr) and arr[i] == target1718# Count occurrences of target19count = bisect.bisect_right(arr, target) - bisect.bisect_left(arr, target)2021# --- Manual binary search ---22lo, hi = 0, len(arr) - 123while lo <= hi:24mid = lo + (hi - lo) // 225if arr[mid] == target:26return mid27elif arr[mid] < target:28lo = mid + 129else:30hi = mid - 1
Common Recipes
1# --- Standard Binary Search ---2def binary_search(nums, target):3lo, hi = 0, len(nums) - 14while lo <= hi:5mid = lo + (hi - lo) // 26if nums[mid] == target:7return mid8elif nums[mid] < target:9lo = mid + 110else:11hi = mid - 112return -11314# --- Find First/Last Position (bisect) ---15import bisect16def search_range(nums, target):17left = bisect.bisect_left(nums, target)18if left >= len(nums) or nums[left] != target:19return [-1, -1]20right = bisect.bisect_right(nums, target) - 121return [left, right]2223# --- Binary Search on Answer Space ---24def min_feasible(lo, hi, is_feasible):25"""Find minimum value in [lo, hi] where is_feasible(x) is True."""26while lo < hi:27mid = lo + (hi - lo) // 228if is_feasible(mid):29hi = mid30else:31lo = mid + 132return lo3334# --- Search in Rotated Sorted Array ---35def search_rotated(nums, target):36lo, hi = 0, len(nums) - 137while lo <= hi:38mid = lo + (hi - lo) // 239if nums[mid] == target:40return mid41if nums[lo] <= nums[mid]: # left half sorted42if nums[lo] <= target < nums[mid]:43hi = mid - 144else:45lo = mid + 146else: # right half sorted47if nums[mid] < target <= nums[hi]:48lo = mid + 149else:50hi = mid - 151return -1
Complexity
| Operation | Time | Space |
|---|---|---|
| Standard search (find target) | O(log n) | O(1) |
| Bisect left / right (find boundary) | O(log n) | O(1) |
| Search in rotated sorted array | O(log n) | O(1) |
| Binary search on answer space | O(log(range) × check) | O(1) |
| Median of two sorted arrays | O(log(min(m, n))) | O(1) |
Watch Out
- ✗The data is **not sorted** and has no monotonic property
- ✗You need to find **all** occurrences, not just one boundary
- ✗The "feasibility" function is not monotonic (feasible → infeasible → feasible)
- ✗The search space cannot be halved by a single comparison
- ✗Integer overflow in mid calculation: `mid = (lo + hi) / 2` overflows for large values. Always use `mid = lo + (hi - lo) / 2`
- ✗Wrong loop condition: Using `lo < hi` for standard search misses the last element. Using `lo <= hi` for boundary search causes infinite loops
- ✗Off-by-one in pointer updates: For `lo <= hi`, both pointers must move past mid (`lo = mid + 1`, `hi = mid - 1`). For `lo < hi`, exactly one pointer stays at mid
- ✗Infinite loop from wrong mid update: If `hi = mid` and `lo = mid` are both possible, the loop never terminates. One side must always move past mid
- ✗Comparing to wrong reference in rotated arrays: In rotated array problems, comparing `nums[mid]` to `nums[lo]` vs `nums[hi]` gives different results — choose carefully