Binary Search Variations: Complete Reference

Type: Software Reference Confidence: 0.95 Sources: 7 Verified: 2026-02-24 Freshness: 2026-02-24

TL;DR

Constraints

Quick Reference

VariationTemplate PatternTimeSpaceKey Insight
Standard (exact match)while lo <= hi, return on matchO(log n)O(1)Classic — search terminates on exact match or empty range
Lower bound (first >=)while lo < hi, hi = midO(log n)O(1)Finds leftmost insertion point; equivalent to C++ lower_bound
Upper bound (first >)while lo < hi, lo = mid + 1O(log n)O(1)Finds rightmost insertion point; equivalent to C++ upper_bound
First true (generalized)while lo < hi, predicate checkO(log n * T(f))O(1)Works on any monotonic predicate, not just arrays
Rotated array minimumwhile lo < hi, compare mid vs hiO(log n)O(1)Compare with rightmost element, not leftmost
Rotated array searchwhile lo <= hi, check sorted halfO(log n)O(1)Identify sorted half first, then check if target is within it
Peak elementwhile lo < hi, compare mid vs mid+1O(log n)O(1)Move toward the ascending side; works on bitonic sequences
Search on answerwhile lo < hi, feasibility checkO(log R * T(check))O(1)Binary search the answer, not the array — verify feasibility
Matrix search (sorted rows+cols)Treat as 1D, or row-then-colO(log(m*n))O(1)Flatten 2D index: row = mid / cols, col = mid % cols
Leftmost / rightmost matchTwo passes: lower_bound + upper_boundO(log n)O(1)Combine to find count of target: upper - lower

Decision Tree

START
├── Is the search space a sorted array?
│   ├── YES → Need exact match?
│   │   ├── YES → Standard binary search (while lo <= hi)
│   │   └── NO → Need first element >= target?
│   │       ├── YES → Lower bound pattern
│   │       └── NO → Need first element > target?
│   │           ├── YES → Upper bound pattern
│   │           └── NO → Need count of target?
│   │               └── YES → lower_bound + upper_bound combo
│   └── NO ↓
├── Is it a rotated sorted array?
│   ├── YES → Need minimum element?
│   │   ├── YES → Rotated array minimum (compare mid vs hi)
│   │   └── NO → Rotated array search (check sorted half)
│   └── NO ↓
├── Is there a monotonic predicate (false→true or true→false)?
│   ├── YES → Is the predicate on the answer itself?
│   │   ├── YES → Search on answer space
│   │   └── NO → First true / last true pattern
│   └── NO ↓
├── Is the array bitonic (increases then decreases)?
│   ├── YES → Peak element pattern
│   └── NO ↓
├── Is it a sorted 2D matrix?
│   ├── YES → Matrix binary search (flatten to 1D)
│   └── NO ↓
└── DEFAULT → Binary search NOT applicable — use hash map, linear scan, or BFS/DFS

Step-by-Step Guide

1. Implement standard binary search

The foundation of all variations. Use while lo <= hi for exact match. [src1]

def binary_search(arr, target):
    lo, hi = 0, len(arr) - 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

Verify: binary_search([1, 3, 5, 7, 9], 5) → expected: 2

2. Implement lower bound (first >= target)

Used for insertion points, counting, and range queries. Never return early — narrow to convergence. [src2]

def lower_bound(arr, target):
    lo, hi = 0, len(arr)       # hi = len(arr), NOT len(arr)-1
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if arr[mid] < target:
            lo = mid + 1
        else:
            hi = mid
    return lo

Verify: lower_bound([1, 3, 5, 5, 7], 5) → expected: 2

3. Implement upper bound (first > target)

Combined with lower bound: range [lower_bound, upper_bound) contains all copies of target. [src2]

def upper_bound(arr, target):
    lo, hi = 0, len(arr)
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if arr[mid] <= target:  # <= instead of <
            lo = mid + 1
        else:
            hi = mid
    return lo

Verify: upper_bound([1, 3, 5, 5, 7], 5) → expected: 4

4. Implement search in rotated sorted array

Determine which half is sorted by comparing arr[lo] vs arr[mid], then check if target lies in the sorted half. [src6]

def search_rotated(arr, target):
    lo, hi = 0, len(arr) - 1
    while lo <= hi:
        mid = lo + (hi - lo) // 2
        if arr[mid] == target:
            return mid
        if arr[lo] <= arr[mid]:           # left half sorted
            if arr[lo] <= target < arr[mid]:
                hi = mid - 1
            else:
                lo = mid + 1
        else:                               # right half sorted
            if arr[mid] < target <= arr[hi]:
                lo = mid + 1
            else:
                hi = mid - 1
    return -1

Verify: search_rotated([4, 5, 6, 7, 0, 1, 2], 0) → expected: 4

5. Implement search on answer space

When you can verify if a candidate works but cannot directly compute the answer, binary search the answer itself. [src4]

def search_on_answer(lo, hi, is_feasible):
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if is_feasible(mid):
            hi = mid
        else:
            lo = mid + 1
    return lo

Verify: search_on_answer(1, 100, lambda x: x * x >= 50) → expected: 8

6. Implement peak element finder

For bitonic arrays, compare arr[mid] with arr[mid+1] to decide direction. [src5]

def find_peak(arr):
    lo, hi = 0, len(arr) - 1
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if arr[mid] < arr[mid + 1]:
            lo = mid + 1
        else:
            hi = mid
    return lo

Verify: find_peak([1, 3, 7, 5, 2]) → expected: 2

Code Examples

Python: Complete binary search toolkit

# Input:  sorted array and target value
# Output: index or insertion point depending on function

from bisect import bisect_left, bisect_right

# Python's standard library provides lower/upper bound:
# bisect_left  = lower_bound (first index >= target)
# bisect_right = upper_bound (first index > target)

arr = [1, 3, 5, 5, 5, 7, 9]
target = 5

first_occurrence = bisect_left(arr, target)      # 2
last_occurrence = bisect_right(arr, target) - 1   # 4
count = bisect_right(arr, target) - bisect_left(arr, target)  # 3

JavaScript: Lower bound and search on answer

// Input:  sorted array, target value
// Output: index of first element >= target

function lowerBound(arr, target) {
  let lo = 0, hi = arr.length;
  while (lo < hi) {
    const mid = lo + ((hi - lo) >> 1);
    if (arr[mid] < target) lo = mid + 1;
    else hi = mid;
  }
  return lo;
}

// Search on answer: find minimum capacity to ship within D days
function shipWithinDays(weights, days) {
  let lo = Math.max(...weights);
  let hi = weights.reduce((a, b) => a + b, 0);
  while (lo < hi) {
    const mid = lo + ((hi - lo) >> 1);
    if (canShip(weights, days, mid)) hi = mid;
    else lo = mid + 1;
  }
  return lo;
}

Java: Overflow-safe binary search

// Input:  sorted int array, target
// Output: index of target, or -1

public static int binarySearch(int[] arr, int target) {
    int lo = 0, hi = arr.length - 1;
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;  // overflow-safe
        if (arr[mid] == target) return mid;
        else if (arr[mid] < target) lo = mid + 1;
        else hi = mid - 1;
    }
    return -1;
}

// Rotated array: find minimum element
public static int findMin(int[] arr) {
    int lo = 0, hi = arr.length - 1;
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (arr[mid] > arr[hi]) lo = mid + 1;
        else hi = mid;
    }
    return arr[lo];
}

C++: Using STL and custom predicates

// Input:  sorted vector, target value
// Output: iterator to first element >= target

#include <algorithm>
#include <vector>

std::vector<int> v = {1, 3, 5, 5, 7, 9};
auto lb = std::lower_bound(v.begin(), v.end(), 5);  // first 5
auto ub = std::upper_bound(v.begin(), v.end(), 5);  // points to 7
int count = std::distance(lb, ub);                    // 2

// Custom predicate with partition_point (C++20):
auto it = std::partition_point(
    v.begin(), v.end(),
    [&](int x) { return (long long)x * x < n; }
);

Anti-Patterns

Wrong: Integer overflow in mid calculation

// BAD — overflows when lo + hi > Integer.MAX_VALUE
int mid = (lo + hi) / 2;

Correct: Overflow-safe mid calculation

// GOOD — subtraction cannot overflow when both are non-negative
int mid = lo + (hi - lo) / 2;
// Or in C++20: std::midpoint(lo, hi)

Wrong: Infinite loop with lo = mid

# BAD — when hi - lo == 1, mid == lo, lo never advances
while lo < hi:
    mid = lo + (hi - lo) // 2  # floor division
    if condition(mid):
        hi = mid
    else:
        lo = mid               # INFINITE LOOP

Correct: Always shrink the search space

# GOOD — use mid + 1 for lo assignment
while lo < hi:
    mid = lo + (hi - lo) // 2
    if condition(mid):
        hi = mid
    else:
        lo = mid + 1           # guarantees progress

Wrong: Off-by-one in search boundaries

# BAD — hi should be len(arr) for lower_bound, not len(arr)-1
def lower_bound(arr, target):
    lo, hi = 0, len(arr) - 1
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if arr[mid] < target:
            lo = mid + 1
        else:
            hi = mid
    return lo  # wrong when target > all elements

Correct: Use half-open interval [lo, hi)

# GOOD — hi = len(arr) handles "target greater than all" correctly
def lower_bound(arr, target):
    lo, hi = 0, len(arr)       # half-open [0, n)
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if arr[mid] < target:
            lo = mid + 1
        else:
            hi = mid
    return lo  # returns len(arr) if target > all

Wrong: Rotated array — comparing with wrong boundary

# BAD — comparing mid with lo for minimum finding
def find_min_rotated(arr):
    lo, hi = 0, len(arr) - 1
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if arr[mid] > arr[lo]:   # wrong comparison
            lo = mid + 1
        else:
            hi = mid
    return arr[lo]  # fails on [2, 1]

Correct: Compare mid with hi for minimum finding

# GOOD — comparing with hi correctly identifies minimum half
def find_min_rotated(arr):
    lo, hi = 0, len(arr) - 1
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if arr[mid] > arr[hi]:   # min in right half
            lo = mid + 1
        else:
            hi = mid
    return arr[lo]  # correctly returns 1 for [2, 1]

Common Pitfalls

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Data is sorted or has a monotonic propertyData is unsorted with no orderingHash table O(1) or linear scan
Need O(log n) search in a static sorted arrayArray is frequently modifiedBalanced BST or skip list
Minimizing/maximizing with a feasibility checkFeasibility function is not monotonicDynamic programming or greedy
Finding boundaries in sorted dataNeed all matching elementsTwo binary searches + iteration
Search space is very large (10^9+)Predicate is expensive and n is smallLinear scan may be faster
Finding peak/valley in bitonic sequencesArray has multiple peaksSegment tree or linear scan

Important Caveats

Related Units