Home/SWE Hub/Binary Search
Back
๐Ÿ”

Algorithms

Binary Search

Binary search is far more than looking up a value in a sorted array โ€” it is a general technique for searching any monotonic predicate in O(log n) time. Mastering the 'search on answer space' pattern unlocks an entire class of problems where the answer itself is binary searched rather than an array index. These patterns appear regularly at Google, Meta, and Amazon in medium-to-hard interview rounds.

Basic array indexingBig-O notationPython/Java syntaxBinary SearchSearch on AnswerRotated ArrayBisectPredicateMonotonicTwo PointersDivide and Conquer

Core Concepts

  • 1Classic binary search โ€” why it works: binary search exploits a sorted array's guarantee that if arr[mid] < target, all elements to the left of mid are also less than target. So we can discard the entire left half in one step. Each step halves the search space: n โ†’ n/2 โ†’ n/4 โ†’ ... โ†’ 1, giving O(log n) iterations. The loop invariant is: 'the answer (if it exists) is always in [left, right]'. Three exit conditions determine the template: (1) find exact value, (2) find first position where condition is true (bisect_left), (3) find last position where condition is true (bisect_right). Getting the boundary conditions right โ€” whether to use left <= right or left < right, and whether to set mid = (l+r)//2 or mid = (l+r+1)//2 โ€” is the main source of bugs.
  • 2Template 1 โ€” exact match (while left <= right): use left=0, right=n-1, mid=(left+right)//2. If arr[mid]==target return mid. If arr[mid]<target set left=mid+1. If arr[mid]>target set right=mid-1. Loop exits when left>right meaning element not found. This is the 'closed interval' template โ€” both left and right are inclusive. The update rules (left=mid+1, right=mid-1) ensure you never revisit mid, preventing infinite loops. Return -1 if not found. Time O(log n), Space O(1).
  • 3Template 2 โ€” first/last occurrence (bisect_left / bisect_right): to find the FIRST position where arr[pos] >= target (lower bound), use left=0, right=n. Loop while left<right. Set mid=(left+right)//2. If arr[mid]<target: left=mid+1 (mid is too small, left side is dead). Else: right=mid (mid might be the answer, keep it). When loop exits, left==right is the answer index. For LAST occurrence (upper bound, first pos where arr[pos]>target), change the condition to arr[mid]<=target: left=mid+1. These two templates form the foundation of Python's bisect_left and bisect_right. Crucial: using right=n (not n-1) allows the answer to be 'insert at the end'.
  • 4Search on answer space โ€” the key paradigm shift: instead of searching for a value in an array, binary search on the ANSWER ITSELF. The question becomes: 'can I achieve result X?' where the feasibility function is monotonic (once X is achievable, all larger X are also achievable, or vice versa). Template: left=min_possible_answer, right=max_possible_answer. While left<right: mid=(left+right)//2; if feasible(mid): right=mid; else: left=mid+1. Return left. This pattern solves: Koko eating bananas (can she eat all bananas in H hours at speed k?), minimum days to make bouquets, capacity to ship packages within D days, split array largest sum. The feasibility check is usually O(n), making total complexity O(n log(answer_range)).
  • 5Rotated sorted array โ€” the insight: after rotation, at least ONE half of the array is still sorted. At index mid, compare with left (or right) endpoint to determine which half is sorted. If arr[left] <= arr[mid], the left half [left..mid] is sorted. If target is in [arr[left], arr[mid]), search left; otherwise search right. If arr[mid] < arr[left], the right half [mid..right] is sorted. If target is in (arr[mid], arr[right]], search right; otherwise search left. The condition arr[left] <= arr[mid] handles the case where the rotation point is in the right half. For duplicates (LC 81), if arr[left]==arr[mid]==arr[right] you can't determine which side is sorted โ€” fall back to left++, right-- (degrades to O(n) worst case).
  • 6Search in 2D matrix: LC 74 (strictly sorted matrix where each row's last element < next row's first) can be treated as a 1D sorted array of n*m elements. Map 1D index i โ†’ (i//cols, i%cols). Standard binary search gives O(log(n*m)). LC 240 (each row and column sorted but rows not connected) requires a different approach: start at top-right corner (or bottom-left). If current > target: move left (current column is too big). If current < target: move down (current row is too small). This staircase search is O(n+m) โ€” not O(log(n*m)), which is actually impossible to achieve in general for LC 240.
  • 7Find peak element โ€” binary search on non-comparison: a peak is defined as an element greater than its neighbors. The key insight: if arr[mid] < arr[mid+1], then the RIGHT half must contain a peak (arr[mid+1] is greater than arr[mid] and if you keep going right and never find a bigger neighbor, you'll hit the right boundary which counts as a peak). So set left=mid+1. If arr[mid] > arr[mid+1], set right=mid (mid itself might be the peak). The loop with left<right converges to a peak. No comparison to a target value โ€” just a local slope direction. This shows binary search applies to ANY property that can tell you 'go left' or 'go right', not just comparisons to a fixed value.
  • 8Median of two sorted arrays โ€” O(log(min(m,n))): the key insight is to binary search on the partition of the SMALLER array. If we put the first k elements of array A in the left partition and the first (total_left - k) elements of array B, we have a valid median partition when: A[k-1] <= B[total_left-k] AND B[total_left-k-1] <= A[k]. Binary search k in [0, len(A)]. This is the hardest binary search problem in interviews โ€” it requires understanding that finding the median is equivalent to finding the right partition, and that binary searching on the partition index is valid because the arrays are sorted. O(log(min(m,n))) time. Only needed at senior/staff level.
  • 9Common bugs and how to avoid them: (1) Infinite loop with left=mid โ€” when target is right=mid, and left=mid would create a cycle. Fix: use mid=(left+right+1)//2 (round up) when right=mid is not chosen. (2) Integer overflow โ€” (left+right) can overflow in Java/C++. Use left + (right-left)//2 instead. Python has arbitrary precision integers so no overflow. (3) Off-by-one in bounds โ€” always test with arrays of size 1, 2, and the target at the extreme ends. (4) Wrong feasibility direction โ€” make sure feasible(mid)=True leads to a smaller search space, not an infinite loop. Sketch the monotonic function before coding.

Key Patterns & Templates

Exact match: left,right=0,n-1; while left<=right: mid=(l+r)//2; if a[mid]==t: return mid; elif a[mid]<t: left=mid+1; else: right=mid-1
Lower bound (bisect_left): left,right=0,n; while left<right: mid=(l+r)//2; if a[mid]<t: left=mid+1; else: right=mid; return left
Upper bound (bisect_right): left,right=0,n; while left<right: mid=(l+r)//2; if a[mid]<=t: left=mid+1; else: right=mid; return left
Search on answer: left,right=lo,hi; while left<right: mid=(l+r)//2; if feasible(mid): right=mid; else: left=mid+1; return left
Rotated array: check if left half sorted (a[l]<=a[mid]); if target in sorted half search there; else search other
Peak element: while left<right: mid=(l+r)//2; if a[mid]<a[mid+1]: left=mid+1; else: right=mid; return left
2D matrix (LC 74): binary search on [0, n*m); map mid -> (mid//cols, mid%cols)
Staircase search (LC 240): start top-right; if cur>t: col--; if cur<t: row++; until found or OOB

Worked Examples

Koko has n piles of bananas and h hours. Each hour she picks one pile and eats at most k bananas from it (leftover counts as a separate hour). Find the minimum integer k such that she can eat all bananas in h hours.

1Step 1 โ€” Recognize the pattern: we are minimizing k. The key observation is that if speed k works (she can finish in h hours), then any speed > k also works. This MONOTONIC property means we can binary search on k itself rather than iterating through all possible speeds.
2Step 2 โ€” Define the search space: minimum possible speed = 1 (she must eat at least 1 per hour). Maximum possible speed = max(piles) (eating the largest pile in one hour is always sufficient). So left=1, right=max(piles).
3Step 3 โ€” Write the feasibility function: given speed k, hours needed = sum(ceil(pile/k) for pile in piles). In Python: math.ceil(pile/k) or equivalently (pile + k - 1) // k. Feasible if total_hours <= h.
4Step 4 โ€” Apply the lower-bound template: we want the SMALLEST k where feasible(k) is True. So when feasible(mid): right=mid (mid could be the answer, keep it). When not feasible(mid): left=mid+1 (mid is too slow, must go higher).
5Step 5 โ€” Verify complexity: the search space is [1, max(piles)] โ‰ˆ 10^9, so log2(10^9) โ‰ˆ 30 iterations. Each feasibility check is O(n). Total O(n log(max_pile)). For n=10^4 and max_pile=10^9, this is ~300,000 operations โ€” perfectly fast.
โœ“ Time O(n log M) where M = max(piles), Space O(1). The template 'when feasible: right=mid; else: left=mid+1; return left' always finds the minimum feasible answer. This exact same template solves LC 1011, LC 410, LC 1482, and dozens of similar problems โ€” recognizing the monotonic feasibility structure is the key skill.

Common Mistakes

โš 

Using left <= right for lower/upper bound templates: the 'find first true' template requires left < right (not <=), with right initialized to n (not n-1). Using <= causes an infinite loop when left==right because mid==left==right and the update right=mid doesn't change anything. Rule: use while left<=right only for exact match; use while left<right for bound-finding.

โš 

Forgetting to handle the 'not found' case in lower bound: lower_bound returns the insertion point, which might be n (element larger than all) or an index where arr[index] != target. Always check after lower_bound: if result == n or arr[result] != target: return 'not found'. This is the most common production bug from direct use of bisect_left.

โš 

Wrong feasibility direction in search-on-answer: the feasibility function must be monotonically True-then-False or False-then-True. If feasible(mid) leads to right=mid, you are minimizing (finding first True). If feasible(mid) leads to left=mid+1, you need NOT feasible to be the condition for right=mid. Drawing the True/False sequence on paper before coding prevents direction bugs.

โš 

In rotated array, using arr[mid] > arr[right] instead of arr[left] <= arr[mid]: both formulations exist but mixing them mid-problem causes bugs. Stick to one: compare arr[left] with arr[mid] to determine which half is sorted. The condition arr[left] <= arr[mid] means left half is sorted (handles the equality edge case for size-2 windows).

โš 

Not testing the single-element and two-element array cases: binary search bugs almost always manifest at array sizes 1 and 2. For size 1: left=right=0, mid=0 โ€” make sure the loop body handles this correctly. For size 2: left=0, right=1, mid=0 โ€” verify mid+1 doesn't go out of bounds (e.g., in peak element where you access nums[mid+1]). Always run through these cases mentally before finalizing.

Only works in the Electron app

<webview> is an Electron-only tag. Run npm run electron:dev to use this.

25:00Focus
0

25 min focus ยท 5 min break ยท long break every 4 sessions

The Ultimate Grind