When the data is sorted, one look at the middle tells you which half holds the answer. So you throw the other half away. Halving 1,000,000 items reaches one item in about 20 looks. Checking them one by one could take 1,000,000 looks.
Think of it like this: the “guess my number between 1 and 100” game. You guess 50, not 1, because “higher” or “lower” rules out half of the numbers at once.
How to spot it
Words and constraints in a problem that point to this pattern:
- “Sorted array” together with “find”, “search” or “position”
- “Return the first (or last) index where…” or “the smallest value that…”
- The prompt asks for O(log n), or n is so large, up to 10⁹, that even one pass is too slow
- “Minimum speed / capacity / days so that it's possible”: if one value works, every bigger value works too
- A sorted array that has been rotated or shifted
Watch it work
O(log n) means the work grows with the number of times you can halve n before reaching 1. For 8 items that's 3 halvings. For 1,000,000 it's about 20.
The most useful form of binary search is this: you have a row of yes/no answers where every “no” comes before every “yes”. Find the first “yes”. “First index where the value is at least 7” and “first bad build” are both this shape.
The example: 8 app builds, numbered 0 to 7. A bug slipped in at some build, and every build after it is broken too. Testing a build takes ten minutes, so we want as few tests as possible. There's an extra slot at index 8 meaning “no build is bad”.
We keep two pointers, lo and hi. Here a pointer is a variable that holds an index. We also keep one invariant: a fact that is true every time the loop starts a new round. Ours is: the first bad build is somewhere in lo..hi. Everything before lo is known good. Everything after hi is known bad.
The template
This template finds the first index in 0..n-1 where isYes(index) is true, and returns n if there is none. Three rules keep it free of off-by-one errors. An off-by-one error means being wrong by exactly one position. The three rules belong together:
histarts atn, one past the end, so “none” is a possible answer.- The loop is
while (lo < hi): it stops when one candidate is left. - On “yes”, set
hi = mid, because mid might be the answer. On “no”, setlo = mid + 1, because mid is definitely not the answer.
You'll also see another style for “is this exact value present?”. It uses hi = n - 1, while (lo <= hi) and hi = mid - 1. It works too. But mixing pieces of the two styles causes endless loops or skipped answers. Pick one style per problem.
// First index in [0, n) where isYes(index) is true; n if there is none.
// Requires: every "no" comes before every "yes".
function firstYes(n: number, isYes: (index: number) => boolean): number {
let lo = 0; // everything before lo is known "no"
let hi = n; // everything from hi onwards is known "yes" (n = one past the end)
while (lo < hi) { // more than one candidate left
const mid = lo + Math.floor((hi - lo) / 2); // middle, rounded down, so mid < hi
if (isYes(mid)) {
hi = mid; // mid might be the first "yes": keep it in the range
} else {
lo = mid + 1; // mid is a "no": the answer is strictly to the right
}
}
return lo; // lo === hi: the first "yes", or n if there was none
}
// Example: the first bad build
const builds = ["ok", "ok", "ok", "ok", "ok", "bad", "bad", "bad"];
const firstBad = firstYes(builds.length, (i) => builds[i] === "bad"); // 5
Worked example
Integer square root: given a whole number n (0 ≤ n ≤ 2³¹), return the largest whole number x with x * x <= n, without Math.sqrt. For n = 10 the answer is 3, because 3 × 3 = 9 but 4 × 4 = 16.
The first idea: try 0, 1, 2, … until the square gets too big, then step back one. For
n = 2,000,000,000that's about 44,700 tries. It works, but the interviewer will ask for better.function intSqrtSlow(n: number): number { let x = 0; while ((x + 1) * (x + 1) <= n) x++; // keep going while the next square still fits return x; }Notice the shape. Ask “is
x * x > n?” for x = 0, 1, 2, and so on. For n = 10 the answers are no, no, no, no, yes, yes, yes… All the “no”s come first. That's exactly the first-yes form, so binary search applies even though there's no array.Frame it with the template: search x in
0..nfor the first x wherex * x > n.hi = n + 1works as “one past the end” because (n + 1)² is always bigger than n. The answer is one less than that first “too big” value.Write it with the three rules.
function intSqrt(n: number): number { let lo = 0; let hi = n + 1; // a "yes" is guaranteed by here while (lo < hi) { const mid = lo + Math.floor((hi - lo) / 2); if (mid * mid > n) hi = mid; // too big: the first "too big" is mid or earlier else lo = mid + 1; // fits: the first "too big" is later } return lo - 1; // lo is the first x with x * x > n }Run
n = 10: lo 0, hi 11 → mid 5, 25 > 10, hi 5 → mid 2, 4 fits, lo 3 → mid 4, 16 > 10, hi 4 → mid 3, 9 fits, lo 4 → stop. Return 4 − 1 = 3. Edge cases:n = 0gives mid 0, 0 fits, lo 1, return 0.n = 1returns 1. About 31 steps for the largest n.
Common mistakes
- Writing
while (lo <= hi)together withhi = mid: whenlo,hiandmidare equal and the check says yes, nothing changes and the loop never ends. - Writing
hi = mid - 1in the first-yes form: whenmidwas the first yes, it's thrown away and you return the last no instead. - Computing
mid = (lo + hi) / 2withoutMath.floor: you get2.5, andarr[2.5]isundefined, so every comparison is wrong. - Starting with
hi = n - 1when “after the last element” or “none” is a valid answer: the search can never returnn. - Binary searching data whose yes/no answers aren't in order, such as an unsorted array: halving can throw away the half that holds the answer. You get a wrong index with no error.
Cost
Each step halves the range: 8 → 4 → 2 → 1. The number of halvings is log₂ n, about 20 for a million and 30 for a billion: O(log n) time. Only lo, hi and mid are stored: O(1) space.
In interviews
Before you write code, state the invariant, the fact that stays true in every round: “the answer is always between lo and hi. Everything before lo is no, and everything from hi on is yes.” Then each update line has an obvious reason, and you can say it out loud. Binary search is asked directly on sorted arrays. It also comes hidden inside rotated-array problems. A third form is “search on the answer”: you binary search over possible speeds or sizes instead of indexes.