Binary Search
A recursive binary search has two base cases: an empty range, which returns -1, and a value found at the middle position, which returns that index.
Try it: Binary Search
How binary search finds a value in a sorted list by checking the middle and throwing away the half that cannot contain it.
How it works
- Start with the whole sorted list as the search interval (lo to hi).
- Look at the middle value.
- If it is the target, stop. If the target is larger, discard the left half; if smaller, discard the right half.
- Repeat until the target is found or the interval is empty (not found).
Default run (7 steps): Search for 17 in [2, 5, 9, 13, 17, 21, 30]. The whole array is the search interval: lo = 0, hi = 6. … 17 = 17 — found at index 4 after 3 comparisons.
Loading the simulation…
A Search That Might Find Nothing
Binary search takes a sorted array and a target value, and returns the position where the target sits, or -1 when the target is absent. The routine has to answer two different questions: where the target is, and whether it is there at all. The second question is easy to overlook, because a routine that only handles successful searches looks correct on most inputs. This article therefore follows the routine's variables step by step and keeps one question in view throughout: how does the routine decide where to look next, and how does it know when to stop?
What do you think happens?
Given the sorted array 3, 7, 11, 15, 19, 24, 30 at indices 0 through 6, what should a binary search for the value 10 return?
Reveal answer
-1
The value 10 is not in the array. The search keeps narrowing until the range is empty, and an empty range means the target cannot be present, so the routine returns -1. The trace in a later section shows each step.
Two Boundaries Instead of a Whole Array
Binary search works on a range of positions rather than on the whole array. The routine keeps track of a lower boundary and an upper boundary, and those two values mark the part of the array that could still contain the target. Each step inspects the item in the middle of that range, uses one comparison to decide which half to keep, and moves one boundary so that the discarded half falls outside the range.
A recursive version needs the same idea expressed as parameters. The original question was find this value in this array. The generalized question is find this value between these two positions of this array, inclusive at both ends. The parameters loIndex and hiIndex carry that range. The value being sought is passed along unchanged. Whole-array search is then just the special case where loIndex is 0 and hiIndex is the array length minus one, so the first call always looks like this.
- The empty-range check comes first, because an empty range has no middle element to compute.
- mid is computed only after the empty-range check has passed.
- Each recursive call receives a strictly smaller range, so the recursion must reach a base case.
Tracing a Search for a Present Value
Take the sorted array 3, 7, 11, 15, 19, 24, 30 at indices 0 through 6, and search for 24. The first call receives the whole range, 0 through 6. The middle position is 3, and the value there is 15. Because 24 is larger than 15, the lower half is discarded, including index 3 itself. The recursive call receives the range 4 through 6. Nothing in the array has changed; only the boundaries have moved.
The middle of the range 4 through 6 is index 5, and the value there is 24. That matches the target, so the search returns 5. The sequence of calls can be read as a stack of recursive invocations, each with its own range, and the answer flows back up from the deepest call.
Searching for 24 in a Seven-Item Array
A = [3, 7, 11, 15, 19, 24, 30], target 24, starting range 0 through 6.
- 1Call 1: range 0 to 6
Middle is index 3, value 15. The target 24 is larger, so the lower half is discarded and the next range is 4 to 6.
- 2Call 2: range 4 to 6
Middle is index 5, value 24. The value equals the target, so this is the successful base case.
- 3Return
The index 5 is returned to the caller and passed back up through the calls.
The search returns 5 after two calls.
Tracing a Search for an Absent Value
Now search the same array for 10, which is not present. The search must rule out every candidate before it can conclude that the target is missing. Each row in the table below shows one call, the range it receives, the middle position it inspects, and the decision it makes.
| Call | loIndex | hiIndex | mid | A[mid] | Decision |
|---|---|---|---|---|---|
| 1 | 0 | 6 | 3 | 15 | 10 is smaller, keep lower half |
| 2 | 0 | 2 | 1 | 7 | 10 is larger, keep upper half |
| 3 | 2 | 2 | 2 | 11 | 10 is smaller, keep lower half |
| 4 | 2 | 1 | none | none | loIndex 2 is greater than hiIndex 1, range is empty, return -1 |
Trace of a recursive binary search for the absent value 10
The fourth call never computes a middle position. Its range is empty, because loIndex has moved past hiIndex, and the empty-range base case returns -1 immediately. This is the reason the empty-range check must come before any computation that depends on the range having elements. An empty range has no middle element, so trying to compute one would be meaningless.
Why Sorted Order Makes One Comparison Enough
Inspecting a single item can rule out a large block of positions, but only because the array is sorted. If the items increase from left to right, comparing the target with one item tells you which side of that item the target must lie on. Everything on the far side can be discarded in one move. Each comparison therefore eliminates about half of the remaining possible positions.
The before and after view above makes the point. One comparison with the middle element 15 removed the lower four positions, leaving three candidates. Without sorting, that same comparison would say nothing about the positions it did not touch, so there would be no safe way to discard them. The discard decisions are valid only when the array increases from left to right.
Counting Steps Against Linear Search
Each step of binary search leaves about half of the remaining locations, so the number of candidates falls quickly. From a range of 1000 locations, the search narrows to about 500, then about 250, then about 125, and so on, until one location remains after about 10 steps. The number of steps is approximately the base-2 logarithm of the number of items, written as log2 n.
| Items n | Binary search steps (about log2 n) | Linear search steps (worst case, n) |
|---|---|---|
| 1,000 | about 10 | up to 1,000 |
| 1,000,000 | about 20 | up to 1,000,000 |
| 1,000,000,000 | about 30 | up to 1,000,000,000 |
Approximate step counts for finding a value in a sorted array
A million items need only about 20 binary search steps. A tenfold or thousandfold increase in items adds only a few more steps, because the count grows with the logarithm of the size. Linear search, which may inspect every element, grows in step with the size of the array itself. The gap between the two methods therefore widens quickly as the array grows.
Mistakes Beginners Make
Common mistake Forgetting the empty-range case
What it looks likeA routine that computes the middle position before checking whether loIndex is greater than hiIndex, then tries to read A at that position.
Why it failsWhen the range is empty there is no middle element, so the computed position refers to nothing meaningful and the routine can fail or return a wrong answer.
Do this insteadCheck loIndex greater than hiIndex first and return -1 before any middle computation.
Common mistake Believing the recursion always ends on the target
What it looks likeWriting the search as a chain of halvings that assumes the target will always turn up.
Why it failsA recursive search is not guaranteed to find the value. It may reach an empty range when the target is absent.
Do this insteadWrite both outcomes as explicit cases: a match at the middle returns its index, and an empty range returns -1.
Common mistake Passing the whole array's bounds into every call
What it looks likeRecursing with the same loIndex and hiIndex each time, or always using 0 and the array length minus one.
Why it failsThe recursive call must search only the part still under consideration, so the boundaries must change on each call.
Do this insteadMove loIndex to mid plus one or hiIndex to mid minus one, so each call works on a strictly smaller range.
Common mistake Searching an unsorted array
What it looks likeApplying the middle comparison to the values 30, 3, 19, 7, 24, 11, 15.
Why it failsThe discard decisions depend on increasing order. Unsorted data can cause the target to be discarded without ever being checked.
Do this insteadSort the array first, or use a method that does not rely on order.
Practice Your Trace
Using the array 3, 7, 11, 15, 19, 24, 30 at indices 0 through 6, trace the recursive binary search for the value 30. For each call, write down loIndex, hiIndex, the middle index, and the value there. State what the top-level call returns.
Hints
- Start with
loIndex0 andhiIndex6, then compute the middle as the floor of the average of the two boundaries. - Expect the search to discard the lower half on each call until index 6 is reached.
- Check your final answer against the base case that returns the index of the matching element.
Key Takeaways
- A recursive binary search has two base cases: an empty range returns -1, and a value found at the middle position returns its index.
- The parameters
loIndexandhiIndexmark the part of the array still under consideration, and each recursive call works on a strictly smaller range. - Binary search depends on a sorted array, because one comparison with the middle element can safely discard half of the remaining positions.
- The number of steps is approximately log2 n, so a million items need about 20 steps, while linear search may need up to a million.