Binary Search
Find a target in a sorted array by repeatedly halving the search range.
Overview
Binary search locates a value in a sorted array in O(log n) comparisons. Instead of scanning left to right, it looks at the middle element and discards the half that cannot contain the target.
The same idea generalizes far beyond arrays: any monotonic predicate (false…false true…true) over an ordered search space can be binary searched — for example the smallest speed at which a task finishes in time.
Intuition
A mental model before the formal terms.
Think of guessing a number between 1 and 100 where each guess is answered "higher" or "lower". Guessing 50 first is optimal: whatever the answer, half the candidates vanish. Seven guesses always suffice because 2^7 > 100.
How it works
- Maintain a range
[lo, hi]that is guaranteed to contain the target if it exists. - Compute
mid = lo + (hi - lo) / 2(avoids overflow in fixed-width integers). - If
a[mid] == target, returnmid. Ifa[mid] < target, the target must be to the right:lo = mid + 1. Otherwisehi = mid - 1. - Stop when
lo > hi— the range is empty and the target is absent.
Why it works
Sortedness gives the invariant: everything left of mid is ≤ a[mid] and everything right is ≥ a[mid]. So one comparison certifies that an entire half cannot hold the target.
Each iteration at least halves the range, so after ⌈log₂(n+1)⌉ iterations the range is empty.
Recognition
How to tell a problem wants this.
- The input is sorted, or can be sorted cheaply, and you need
O(log n)lookups. - The problem asks for the minimum/maximum value satisfying a condition and the condition is monotonic ("smallest capacity such that…").
- Constraints like
n ≤ 10^5with many queries, or an answer range up to10^9, hint at searching the answer space.
Interactive visualization
Play, step, change the input. ← → and space work too.
1lo = 0, hi = n - 12while lo <= hi:3 mid = lo + (hi - lo) // 24 if a[mid] == target: return mid5 if a[mid] < target: lo = mid + 16 else: hi = mid - 17return -1Pseudocode
1lo = 0, hi = n - 12while lo <= hi:3 mid = lo + (hi - lo) // 24 if a[mid] == target: return mid5 if a[mid] < target: lo = mid + 16 else: hi = mid - 17return -1Implementations
1from bisect import bisect_left2from typing import Callable, Sequence3 4 51 · The exact-match form on a sorted sequence6def binary_search(a: Sequence[int], target: int) -> int:7 lo, hi = 0, len(a) - 18 while lo <= hi:9 mid = (lo + hi) // 2 # Python ints are unbounded, so no overflow10 if a[mid] == target:11 return mid12 if a[mid] < target:13 lo = mid + 114 else:15 hi = mid - 116 return -117 18 192 · lower_bound: first index whose value is >= target (the useful form)20def lower_bound(a: Sequence[int], target: int) -> int:21 lo, hi = 0, len(a) # half-open [lo, hi)22 while lo < hi:23 mid = (lo + hi) // 224 if a[mid] < target:25 lo = mid + 126 else:27 hi = mid28 return lo # in [0, n]; a[lo] >= target, or lo == n if none is29 30 313 · Binary search on the answer: smallest x in [lo, hi] with pred(x) true32def search_answer(lo: int, hi: int, pred: Callable[[int], bool]) -> int:33 while lo < hi:34 mid = (lo + hi) // 235 if pred(mid):36 hi = mid37 else:38 lo = mid + 139 return lo # requires pred to be False...False,True...True over [lo, hi]40 41 424 · The standard library already implements lower_bound as bisect_left43def present(a: Sequence[int], target: int) -> bool:44 i = bisect_left(a, target)45 return i < len(a) and a[i] == target(lo + hi) // 2is safe in Python because integers are arbitrary precision — the overflow-avoidance dance in the other three languages is unnecessary here.lo, hi = 0, len(a)uses the half-open convention thatbisectitself uses, so the hand-writtenlower_boundandbisect_leftagree exactly.search_answeris the monotone-predicate form;bisectcannot express it directly because it searches a sequence, not a predicate.bisect_left(a, target)*is*lower_bound;presentis the standard two-line exact-match wrapper around it.//is floor division, which for the non-negative indices here is the same as truncation — but not for negative values, where-1 // 2 == -1.
bisect is implemented in C, so it is much faster than the Python-level loop at the same asymptotic cost.
bisect.bisect_leftandbisect.bisect_rightarelower_boundandupper_bound;insort_left/insort_rightinsert while keeping the list sorted (O(n) for the shift).bisectaccepts akey=argument since Python 3.10, which removes the old trick of maintaining a parallel list of keys.- Arbitrary-precision integers mean
(lo + hi) // 2never overflows — the JDK bug simply cannot happen in Python. sorted()andlist.sort()are stable TimSort, andsorted()on mixed types raisesTypeErrorrather than producing a silently wrong order.
- Using
bisect_rightwherebisect_leftis meant: on duplicates they return opposite ends of the run, which flips the answer to "first occurrence" queries. - Calling
insortin a loop to build a sorted list, which is O(n²); collect thensort()once instead. - Mixing the closed
while lo <= hiloop withhi = len(a), which raisesIndexErroron the first iteration.
- Overflow:
(lo + hi) / 2overflows a 32-bitintin C++ and truncates past 2^31 under>>in JS/TS; Python integers are unbounded, so only Python can write the naive midpoint safely. - Standard library: C++ has
binary_search/lower_bound/upper_bound/equal_range, Python hasbisect_left/bisect_right/insort, and JavaScript and TypeScript have nothing at all. - Default sort order: JavaScript
Array.prototype.sort()compares as strings unless given a comparator, so a "sorted" numeric array may not be sorted — a precondition violation the search cannot detect. - Comparator convention: C++ and the TypeScript version here take strict-less predicates;
Array.prototype.sorttakes a signed number;bisecttakes an optionalkeyprojection instead of a comparator.
Complexity
Recursive version uses O(log n) stack space.
Compare growth rates in the Complexity Explorer →When to use — and when not to
- Sorted data with many lookups.
- Monotonic answer spaces ("minimum X such that feasible(X)").
- Finding boundaries: first/last occurrence, insertion point, rotation point.
- Unsorted data that is queried once — sorting costs O(n log n), more than a linear scan.
- Linked lists — no O(1) random access, so halving does not save work.
- Tiny arrays where a linear scan is faster in practice due to branch prediction.
Alternatives
Common mistakes
- Off-by-one on
hi = midvshi = mid - 1leading to infinite loops. - Using
(lo + hi) / 2in fixed-width languages — overflows for large indices. - Forgetting that the array must be sorted (or the predicate monotonic).
- Mixing the two templates (
lo <= hireturning-1vslo < hireturning a boundary).
Interview patterns
- Search a rotated sorted array by deciding which half is sorted.
- Binary search on the answer: Koko eating bananas, split array largest sum, capacity to ship packages.
- First/last position of an element (lower/upper bound).
- Search in a 2D sorted matrix by treating it as a flattened array.
- Recognizing the approach from an array and a targetIntermediate
- Deciding whether O(n²) can be improvedIntermediate
- Where does O(n log n) come from?Beginner
- Questions to ask before binary searchingIntermediate
- Minimum Size Subarray SumIntermediate
- Search in Rotated Sorted ArrayIntermediate