Out of Loop

The Art of Binary Search

An interactive guide to halving your way to the answer.

I'm thinking of a number between 1 and 100. Each time you guess, I'll tell you whether my number is higher or lower. What's your first guess?

If you said 50, you already know binary search. No matter what I say, half of the possible numbers are gone in a single guess. Keep guessing the middle, and you'll find any number in at most seven tries.

That same idea lets a computer search a sorted list of a billion items in about 30 steps.

The setup

Binary search has one requirement: the data must be sorted. Given a sorted array and a target, we want the index of the target, or to know that it isn't there.

We keep track of two positions, low and high, which mark the part of the array that could still contain the target. At the start, that's the entire array.

Problem

How do we shrink the range between low and high as fast as possible, without ever skipping over the target?

Halving the range

At each step, we look at the element in the middle of the range:

Pick a target and step through the search. L, M and H mark low, mid and high:

Tip: click any number to search for it.

  1. 0L
  2. 1
  3. 2
  4. 3
  5. 4
  6. 5
  7. 6
  8. 7
  9. 8
  10. 9
  11. 10
  12. 11
  13. 12
  14. 13
  15. 14
  16. 15H

Looking for 58. The whole array is still in play.

binary: 0 · linear: 12

Click a number (or type one that isn't in the array) and press Step. Crossed-out numbers have been ruled out.

The code

Here's that process written out in JavaScript:

function binarySearch(array, target) {  let low = 0;  let high = array.length - 1;  while (low <= high) {    const mid = Math.floor((low + high) / 2);    if (array[mid] === target) return mid;    if (array[mid] < target) low = mid + 1;    else high = mid - 1;  }  return -1;}

It's short, but almost every character matters.

Where the bugs hide

Binary search is notorious for off-by-one errors. Here are the three most common ones:

  1. Using low < high instead of low <= high. When low and high point to the same element, that element hasn't been checked yet. Stop the loop early and you'll miss targets at the edges.
  2. Setting low = mid instead of low = mid + 1. We already know array[mid] isn't the target. Keeping it in the range can leave the loop stuck on the same two elements forever.
  3. Overflowing on low + high. In languages with fixed-size integers, adding two large indexes can overflow. Writing low + Math.floor((high - low) / 2) avoids it. JavaScript numbers are large enough that this rarely matters, but it famously broke Java's own implementation for years.

Try the target 4 in the figure above. It isn't in the array, so watch how low ends up just past high — that crossover is exactly what low <= high is checking for.

Why it's so fast

Each step cuts the remaining range in half. Starting with n elements, after k steps we have n / 2ᵏ left, and we stop when that reaches one. That makes binary search O(log n).

ItemsLinear search (worst case)Binary search (worst case)
16165
1,0001,00010
1,000,0001,000,00020

Every time the data grows a thousand times bigger, binary search needs only about ten more steps. Not bad for an algorithm you already knew as a guessing game.