BeeTool.Dev - Essential Tools for developers

Stories.Tool Tabs VaultPersonal Documents
Super Debugging Context
BeeTool.Dev

Essential tools, knowledge, and playgrounds for developers.

Explore

  • Stories
  • Personal Documents
  • Features
  • Algorithms Playground

Tool Tabs

  • JSON Compare
  • SQL Formatter
  • Text Diff
  • All tools →

Learn

  • Sorting algorithms
  • Searching algorithms
  • Algorithm Race Mode
© 2024 – 2026 BeeTool.DevPrivacy policy
← Algorithms Playground
Searching · Intermediate

Binary Search

Halve the search space on every comparison — a sorted array of a billion elements falls in about 30 probes.

AverageO(log n)StructureSorted arrayNeeds sortedYesComparisonYes
Must remember

Probe the middle of a sorted window, keep the half that can still contain the target, repeat.

Best O(1) · Avg O(log n) · Worst O(log n) · Space O(1)

Hands on Interactive visualization

Size 24
Target 45
ProbingFoundUnexploredEliminated
0/0Ready — press play
Speed
Step 0 / 0
Comparisons
0
Swaps
0
Reads
0
Writes
0
Elapsed
0.0s
Frames
0
Depth
0 / 0
Aux memory
0 B

Implementation Code

export function binarySearch(a: number[], target: number): number {
  let lo = 0;
  let hi = a.length - 1; // closed interval [lo, hi]
  while (lo <= hi) {
    const mid = lo + ((hi - lo) >> 1); // overflow-safe midpoint
    if (a[mid] === target) return mid;
    if (a[mid] < target) {
      lo = mid + 1; // discard left half including mid
    } else {
      hi = mid - 1; // discard right half including mid
    }
  }
  return -1;
}

Big-O Complexity

Best
O(1)
Average
O(log n)
Worst
O(log n)
Space
O(1)

O(1) space for the iterative form; the recursive form spends O(log n) stack. The log n probes are random-access, so each one is a potential cache miss on huge arrays.

In production When to use

Stdlib primitives

Every mainstream standard library ships it: C++ std::lower_bound / upper_bound, Python's bisect module, Java's Arrays.binarySearch and Collections.binarySearch, Go's sort.Search and slices.BinarySearch, Rust's slice::binary_search.

Database indexes

B-tree index lookups binary-search the sorted keys within each node page to pick the child pointer — a binary search at every level of the tree, on every indexed query.

Debugging / git bisect

git bisect binary-searches commit history for the commit that introduced a bug: thousands of commits are narrowed to one in roughly a dozen test runs.

Binary search on the answer

Optimization problems with a monotonic feasibility check — smallest capacity that ships packages in D days, rate limiting thresholds, capacity planning — are solved by binary-searching the answer space rather than an array.

Sorted on-disk data

Log-structured storage (LSM SSTables, sorted run files, sparse index blocks) locates keys in immutable sorted files by binary search over block offsets.

Runtime internals

Exception-table and line-number lookups, symbol resolution in sorted symbol tables, and unwind-info lookups in language runtimes are binary searches over sorted address ranges.

Trade-offs Strengths & weaknesses

  • O(log n) worst case — a billion elements in ~30 comparisons.
  • O(1) extra space in the iterative form.
  • The lower_bound variant finds insertion points and range boundaries, not just membership.
  • Generalizes beyond arrays: any monotonic predicate over an ordered domain.
  • No extra data structure to maintain — a sorted array is its own index.
  • Cache-compact: a sorted array has zero per-element pointer overhead, unlike trees.
  • Requires sorted input — sorting first costs O(n log n) and must be maintained.
  • Insertions and deletions in a sorted array are O(n); dynamic data wants a tree instead.
  • Notoriously easy to get subtly wrong: boundaries, midpoints, termination.
  • Random-access probes are cache-hostile on very large arrays compared to a linear scan of small ones.
  • Only answers order-based queries — a hash table beats it for pure equality lookups.

Intuition Mental model

The number-guessing game

Someone picks a number between 1 and 1000 and answers only higher or lower. Guessing the middle every time is binary search — and 10 guesses always suffice, because each answer discards half of what remains.

  1. Guess the exact middle of the current range — any other guess splits the range unevenly and wastes information.
  2. Higher discards the bottom half including your guess; lower discards the top half.
  3. Repeat on the surviving half: 1000 becomes 500, 250, 125... one number in 10 steps.
  4. The range empty without a match is proof the number was never there.
  5. Same instinct as a dictionary: open near the middle, decide left or right, never re-read a discarded half.

Prep Interview questions

Implement binary search without an off-by-one error. What invariant do you maintain?
Pick one convention and never mix: half-open [lo, hi) with while lo < hi, or closed [lo, hi] with while lo <= hi. State the invariant (target, if present, is always inside the window) and check it survives every branch.
Why is mid = (lo + hi) / 2 a famous bug, and what is the fix?
lo + hi overflows fixed-width integers on huge arrays — the bug that lived in Java's own Arrays.binarySearch for years. Write lo + (hi - lo) / 2, or an unsigned shift where available.
Search a rotated sorted array in O(log n).
At every step, one half of the window is still properly sorted — check its endpoints to decide whether the target lies inside that half, and recurse into the correct side.
Find the first and last occurrence of a duplicated key.
Run two biased binary searches: lower_bound (first index where a[i] >= target) and upper_bound (first index where a[i] > target). The range is [lower, upper).
What does binary search look like when there is no array at all?
Binary search on the answer: any monotonic predicate (can we do it with capacity x?) splits the domain into false...false true...true — binary search finds the boundary.

Watch out Common bugs

Infinite loop from lo = mid

With integer division, mid can equal lo when the window has two elements; setting lo = mid then never shrinks the window. Use lo = mid + 1 (closed bounds) or bias the midpoint upward when the update is hi = mid.

Midpoint overflow: (lo + hi) / 2

In fixed-width integer languages, lo + hi overflows for arrays over a billion elements. Always write lo + (hi - lo) / 2.

Wrong boundary update direction

Writing hi = mid when the convention needs hi = mid - 1 (or vice versa) either drops the answer from the window or re-examines mid forever. The fix is mechanical once you commit to closed or half-open bounds.

Searching data that is not actually sorted

Binary search on unsorted or partially sorted data returns confidently wrong answers with no error. Comparator mismatches (sorting ascending, searching with a descending comparator) cause the same silent failure.

One screen Cheat sheet

Probe the middle of a sorted window, keep the half that can still contain the target, repeat.

Best O(1) · Avg O(log n) · Worst O(log n) · Space O(1)
  • Requires sorted data — that precondition is the whole contract.
  • mid = lo + (hi - lo) / 2 to dodge integer overflow.
  • Commit to one bound convention: closed [lo, hi] with lo <= hi, or half-open [lo, hi) with lo < hi.
  • lower_bound / upper_bound answer insertion-point and range queries, not just membership.
  • Generalizes to any monotonic predicate — binary search on the answer.
  • Stdlib names: std::lower_bound, bisect, Arrays.binarySearch, sort.Search, slice::binary_search.
Pick whenData is sorted (or sortable once) and read-heavy, or the problem has a monotonic yes/no boundary.
Avoid whenData is unsorted and queried once, mutates constantly, or you only need equality — use a scan, a tree, or a hash table.

Linear Search

Jump Search