Binary Search Algorithm Explained with Examples

By · · Algorithms

Binary search is one of those algorithms that seems trivial until you try to implement it correctly under pressure. The concept is straightforward: cut the search space in half each time. But off-by-one errors in the boundaries have caused more bugs than most people would like to admit.

The prerequisite is simple. Your data needs to be sorted. If it's not sorted, binary search won't work. That's the deal.

Interactive Visualization

Use the controls below to step through the algorithm or let it play automatically.

name: binary-search
input: {"array": [2, 5, 8, 12, 16, 23, 38, 56, 72, 91], "target": 23}
speed: 500

How Binary Search Works

You have a sorted array and you're looking for a target value. Instead of checking every element from left to right (linear search), you look at the middle element first. If the middle element is your target, great, you're done. If the target is smaller, you know it must be in the left half. If it's bigger, it's in the right half. Now repeat with just that half.

Each comparison eliminates half the remaining elements. That's why it's O(log n) instead of O(n).

Visualizing the Search Space

Three comparisons to find our target in a 10-element array. A linear search might have needed 6. The difference gets dramatic with larger arrays. For a million elements, binary search needs at most 20 comparisons. Linear search could need a million.

Step-by-Step Walkthrough

Let's search for 23 in [2, 5, 8, 12, 16, 23, 38, 56, 72, 91].

Step 1

[2, 5, 8, 12, 16, 23, 38, 56, 72, 91]
 ^                                  ^
low=0                           high=9
                  ^
                mid=4
              arr[4]=16

16 < 23, so target is in the right half.
Move low to mid + 1 = 5.

Step 2

[2, 5, 8, 12, 16, 23, 38, 56, 72, 91]
                    ^               ^
                  low=5          high=9
                            ^
                          mid=7
                        arr[7]=56

56 > 23, so target is in the left half.
Move high to mid - 1 = 6.

Step 3

[2, 5, 8, 12, 16, 23, 38, 56, 72, 91]
                    ^   ^
                  low=5 high=6
                    ^
                  mid=5
                arr[5]=23

23 == 23. Found it at index 5!

Three steps. Each time we cut the search space roughly in half. That's the power of logarithmic time.

How the Search Space Shrinks

Python Implementation

Iterative Version

def binary_search(arr, target):
    low = 0
    high = len(arr) - 1

    while low <= high:
        mid = low + (high - low) // 2  # Avoids integer overflow

        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            low = mid + 1
        else:
            high = mid - 1

    return -1  # Target not found


arr = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]
print(binary_search(arr, 23))   # 5
print(binary_search(arr, 15))   # -1

A couple of things to notice. We use low + (high - low) // 2 instead of (low + high) // 2. In Python this doesn't matter because integers can be arbitrarily large, but in languages like Java or C++ the addition can overflow. It's a good habit to keep.

The loop condition is low <= high, not low < high. That equals sign matters. Without it, you'd miss the case where the target is the last remaining element.

Recursive Version

def binary_search_recursive(arr, target, low=0, high=None):
    if high is None:
        high = len(arr) - 1

    if low > high:
        return -1

    mid = low + (high - low) // 2

    if arr[mid] == target:
        return mid
    elif arr[mid] < target:
        return binary_search_recursive(arr, target, mid + 1, high)
    else:
        return binary_search_recursive(arr, target, low, mid - 1)


arr = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]
print(binary_search_recursive(arr, 23))  # 5
print(binary_search_recursive(arr, 15))  # -1

The iterative version is generally preferred. It uses O(1) space while the recursive version uses O(log n) stack space. The logic is identical, you're just replacing the recursion with a while loop.

Useful Variations

Find the Leftmost (First) Occurrence

When duplicates exist, standard binary search might land on any one of them. This version finds the first occurrence.

def find_leftmost(arr, target):
    low = 0
    high = len(arr) - 1
    result = -1

    while low <= high:
        mid = low + (high - low) // 2

        if arr[mid] == target:
            result = mid       # Record this, but keep searching left
            high = mid - 1
        elif arr[mid] < target:
            low = mid + 1
        else:
            high = mid - 1

    return result


arr = [1, 3, 3, 3, 5, 7, 9]
print(find_leftmost(arr, 3))  # 1 (first occurrence)

The trick is that when we find the target, we don't stop. We record the position and keep searching the left half for an even earlier occurrence.

Search Insert Position

This is a classic LeetCode problem. Find where a target would be inserted to keep the array sorted.

def search_insert(arr, target):
    low = 0
    high = len(arr) - 1

    while low <= high:
        mid = low + (high - low) // 2

        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            low = mid + 1
        else:
            high = mid - 1

    return low  # This is the insertion point


arr = [1, 3, 5, 6]
print(search_insert(arr, 5))  # 2 (found at index 2)
print(search_insert(arr, 2))  # 1 (would insert at index 1)
print(search_insert(arr, 7))  # 4 (would insert at the end)

When the loop ends without finding the target, low points to exactly where it should go. This works because low always ends up at the first element greater than the target.

Binary Search on a Rotated Sorted Array

This is a trickier variation. The array was sorted but then rotated at some pivot. For example, [4, 5, 6, 7, 0, 1, 2] is [0, 1, 2, 4, 5, 6, 7] rotated at index 4.

def search_rotated(arr, target):
    low = 0
    high = len(arr) - 1

    while low <= high:
        mid = low + (high - low) // 2

        if arr[mid] == target:
            return mid

        # Figure out which half is sorted
        if arr[low] <= arr[mid]:
            # Left half is sorted
            if arr[low] <= target < arr[mid]:
                high = mid - 1
            else:
                low = mid + 1
        else:
            # Right half is sorted
            if arr[mid] < target <= arr[high]:
                low = mid + 1
            else:
                high = mid - 1

    return -1


arr = [4, 5, 6, 7, 0, 1, 2]
print(search_rotated(arr, 0))  # 4
print(search_rotated(arr, 3))  # -1

The key insight here is that in a rotated sorted array, at least one half is always properly sorted. We figure out which half is sorted, then check if the target falls within that sorted half. If it does, search there. If not, search the other half.

Time and Space Complexity

Case Time Complexity Space Complexity
Best O(1) O(1)
Average O(log n) O(1)
Worst O(log n) O(1)

Best case is O(1) when the target happens to be the middle element on the first check. Worst and average cases are both O(log n) because we halve the search space each iteration.

Space is O(1) for the iterative version. The recursive version uses O(log n) due to the call stack.

To put the time complexity in perspective:

Array Size Linear Search (worst) Binary Search (worst)
100 100 comparisons 7 comparisons
10,000 10,000 comparisons 14 comparisons
1,000,000 1,000,000 comparisons 20 comparisons

That difference is massive at scale.

When Should You Use Binary Search?

Finding elements in sorted arrays. This is the obvious one. If your data is sorted and you need to look things up repeatedly, binary search is the way to go. Python's bisect module uses binary search under the hood.

Searching in rotated sorted arrays. Databases and distributed systems sometimes deal with rotated or partially sorted data. The modified binary search handles this without needing to "un-rotate" the array first.

Finding boundaries. Questions like "what's the first number greater than X" or "what's the last number less than or equal to Y" are natural fits. These show up constantly in range queries and scheduling problems.

Searching on monotonic functions. Binary search doesn't just work on arrays. Any time you have a yes/no condition that flips from false to true (or true to false) as you move in one direction, you can binary search for the flip point. Square root calculation, finding minimum capacity, allocating resources, these all use this pattern.

Common Interview Problems

Binary search is one of those foundational tools you'll reach for over and over. Get the template solid in your head, understand the boundary conditions, and the variations become much easier to reason about.