Skip to content
ByteStepStart with recursion

Binary search, and why it is easy to get wrong

Free, no account

Looking something up in a sorted list is the one place where knowing an algorithm pays off immediately. Check the middle. If it is too small, everything to its left is too small as well, so all of it can go. If it is too big, the right half goes instead. Repeat on what is left.

The part worth watching is what that discards. Below, 16 sorted numbers, and a search for 23 settles it in 2 comparisons while looking at 2 of the 16 cells. The others are not skipped over quickly. They are never read at all.

Drag the target. Odd numbers are in the array and even ones are not, so you can watch a successful search and a failed one back to back. Then switch to linear to see what ignoring the sorting costs, and to buggy for the version almost everyone writes at least once.

1function search(a, target) {
2 let lo = 0, hi = a.length - 1
3 while (lo <= hi) {
4 const mid = (lo + hi) >> 1
5 if (a[mid] === target) return mid
6 if (a[mid] < target) lo = mid + 1
7 else hi = mid - 1
8 }
9 return -1
10}

The array

sorted, with the part still worth looking at

Looking for 23

  1. 1
  2. 3
  3. 5
  4. 7
  5. 9
  6. 11
  7. 13
  8. 15
  9. 17
  10. 19
  11. 21
  12. 23
  13. 25
  14. 27
  15. 29
  16. 31

16 of 16 still in play · 0 comparisons

Sixteen sorted numbers, and we are looking for 23. Everything is still in play, so the window covers all sixteen.

1/5
Detail
5 steps

How the window halves, why sorting is load-bearing, and what the bug actually does.

Throwing away half, not looking twice as fast

The rule under the cells is the window: the part of the array that could still hold the answer. It starts covering everything and never grows. Every comparison either lands on the target or rules out one side of the cell you just checked, which is half of what was left.

That is why the discarded cells stay on screen rather than vanishing. The array is not getting smaller. Your obligation to look at it is.

Why it has to be sorted

Everything rests on one inference: if the middle value is too small, every value to its left is too small as well. That is only true because the array is in order. Hand the same algorithm an unsorted array and it will confidently discard the half containing the answer, then report that the answer is not there. It will not crash, and it will not look wrong.

Absence costs what presence costs

Search for 24. It is not in the array, and finding that out takes the same handful of comparisons that finding 23 did. The window halves until it is empty, and an empty window is proof: not a failure to look hard enough, but a demonstration that there was nowhere left the value could have been hiding.

Now switch to linear and search for 31, the last value. That takes 16 comparisons, because linear search has no way to rule anything out and has to walk the whole row. It is reading the same numbers; it just learns almost nothing from each one.

The bug

Switch to buggy and search for an even number below 31. The only difference is on line 7: the high end of the window moves to mid rather than mid - 1, so the cell that was just ruled out stays in the window.

Watch what that does. The window shrinks normally until it holds one cell, and then stops. The loop keeps checking the same cell, gets the same answer, and narrows to the same window. Nothing crashes. No wrong value is returned. The search simply never ends, and on screen it looks almost exactly like a search that is still working.

Know someone stuck on this? Send it to them.

A classmate, a study group, someone learning this on their own at midnight. The link opens the lesson set up exactly as you have it, the same function and the same number, and runs from the first step so they watch the whole thing build rather than landing in the middle of it.

Also in this course

Recursion: how a function calls itself

The call tree and the call stack side by side, so you can watch a call pause, wait, and pick up exactly where it left off.

Start there →