Binary Search: Explained Visually

Updated 02 Nov 202612 min readLevel: Beginner2 interactive visuals

Binary search is a way to find an item in a sorted list by checking the middle element and throwing away the half where the item cannot be. Each check cuts the search space in half, so even a list of 1 billion items needs only about 30 checks. It is one of the most important algorithms for coding interviews, and the same idea powers database indexes, git bisect and many optimisation problems.

What you will learn
  • The idea behind binary search, and when you can use it
  • How low, mid and high move, step by step
  • Why it takes O(log n) time
  • Iterative and recursive code in Python, Java and C++
  • Lower bound, first/last occurrence and binary search on the answer
  • The bugs that fail most interview solutions
Contents
  1. The idea: the dictionary trick
  2. Binary search, step by step
  3. Why binary search is so fast: O(log n)
  4. Binary search code
  5. Variations you must know
  6. Common bugs (and how to avoid them)
  7. Where binary search is used in real life

The idea: the dictionary trick

When you look up the word "neuron" in a printed dictionary, you do not start at page 1. You open it near the middle, see "lemon", and know neuron must be after it. You throw away the first half and repeat with the second half. In a few flips you are on the right page.

Binary search does exactly this with an array. It only works when the data is sorted (or, more generally, when you can always tell which half the answer is in).

Binary search, step by step

We keep two pointers, low and high, around the part of the array where the target could still be. At each step:

  1. Find the middle: mid = (low + high) / 2 (rounded down).
  2. If arr[mid] == target, we found it.
  3. If arr[mid] < target, the target must be on the right, so set low = mid + 1.
  4. If arr[mid] > target, the target must be on the left, so set high = mid − 1.
  5. If low > high, the range is empty, so the target is not in the array.

Try it below. Pick a value that is present, then one that is missing, and step through.

Why binary search is so fast: O(log n)

Every check halves the remaining items: n → n/2 → n/4 → … → 1. The number of halvings needed to get from n down to 1 is log2(n). Drag the slider to feel the difference:

Array size n Linear search (worst case) Binary search (worst case)
100 100 checks 7 checks
1,000 1,000 10
1,000,000 1,000,000 20
1,000,000,000 1,000,000,000 30
Complexity Value
Best case O(1): the first mid is the target
Average and worst case O(log n)
Space (iterative) O(1)
Space (recursive) O(log n), for the call stack
The catch: it must be sorted

Sorting costs O(n log n). If you only search once, a simple O(n) linear scan is cheaper than sorting first. Binary search pays off when you search the same sorted data many times, or when the data is already sorted.

Binary search code

Iterative binary search in Python

PYTHON
def binary_search(arr, target):
    low, high = 0, len(arr) - 1
    steps = 0
    while low <= high:
        mid = (low + high) // 2
        steps += 1
        if arr[mid] == target:
            return mid, steps
        elif arr[mid] < target:
            low = mid + 1          # target is in the right half
        else:
            high = mid - 1         # target is in the left half
    return -1, steps

arr = [3, 8, 12, 17, 21, 26, 31, 38, 44, 52, 57, 63, 70, 81, 95]
print(binary_search(arr, 57))
print(binary_search(arr, 40))
OUTPUT
(10, 4)
(-1, 4)

57 is found at index 10 in 4 checks. 40 is not in the array, and binary search proves that in 4 checks too, while a linear search would need all 15.

The same code in other languages

JAVA
static int binarySearch(int[] arr, int target) {
    int low = 0, high = arr.length - 1;
    while (low <= high) {
        int mid = low + (high - low) / 2;   // avoids integer overflow
        if (arr[mid] == target) return mid;
        if (arr[mid] < target) low = mid + 1;
        else high = mid - 1;
    }
    return -1;
}
C++
int binarySearch(const vector<int>& arr, int target) {
    int low = 0, high = (int)arr.size() - 1;
    while (low <= high) {
        int mid = low + (high - low) / 2;   // avoids integer overflow
        if (arr[mid] == target) return mid;
        if (arr[mid] < target) low = mid + 1;
        else high = mid - 1;
    }
    return -1;
}
// The STL has it built in: std::binary_search, std::lower_bound, std::upper_bound
PYTHON
def binary_search_rec(arr, target, low, high):
    if low > high:
        return -1                                   # empty range: not found
    mid = (low + high) // 2
    if arr[mid] == target:
        return mid
    if arr[mid] < target:
        return binary_search_rec(arr, target, mid + 1, high)
    return binary_search_rec(arr, target, low, mid - 1)
Why low + (high − low) / 2?

In Java and C++, low + high can be larger than the maximum int (about 2.1 billion) for big arrays, which overflows to a negative number. low + (high - low) / 2 gives the same middle without overflow. Python integers never overflow, so (low + high) // 2 is fine there. Interviewers love this question.

Variations you must know

Plain binary search answers "is it there?". Most interview problems need a variation.

Lower bound: first position where value ≥ target

This version never stops early. It keeps shrinking until low == high, which is the first index whose value is greater than or equal to the target. With it you can find the first occurrence, the last occurrence, the count of a value, and where to insert a new value.

PYTHON
def lower_bound(arr, target):
    """First index whose value is >= target (where target would be inserted)."""
    low, high = 0, len(arr)          # note: high = len(arr), and low < high
    while low < high:
        mid = (low + high) // 2
        if arr[mid] < target:
            low = mid + 1
        else:
            high = mid
    return low

marks = [35, 42, 42, 42, 58, 67, 91]
first = lower_bound(marks, 42)
last = lower_bound(marks, 43) - 1
print("first 42 at", first, "| last 42 at", last, "| count =", last - first + 1)
print("insert 60 at index", lower_bound(marks, 60))

import bisect
print(bisect.bisect_left(marks, 42), bisect.bisect_right(marks, 42))
OUTPUT
first 42 at 1 | last 42 at 3 | count = 3
insert 60 at index 5
1 4

Python's built-in bisect_left is exactly lower bound, and bisect_right gives the position just after the last equal value.

Binary search on the answer

This is the most powerful idea. Sometimes you are not searching an array at all. You are searching for the smallest (or largest) value that works, and you can check any candidate quickly. If "works" is monotonic (once a value works, every bigger value also works), you can binary search over the possible answers.

Problem (Koko eating bananas): there are piles of bananas, and Koko has h hours. Each hour she eats up to k bananas from one pile. What is the smallest speed k that lets her finish in time?

PYTHON
import math

def min_speed(piles, hours):
    """Smallest eating speed k so that all piles finish within `hours`."""
    def can_finish(k):
        return sum(math.ceil(p / k) for p in piles) <= hours

    low, high = 1, max(piles)
    while low < high:
        mid = (low + high) // 2
        if can_finish(mid):
            high = mid        # mid works, try slower
        else:
            low = mid + 1     # too slow, go faster
    return low

print(min_speed([3, 6, 7, 11], 8))
print(min_speed([30, 11, 23, 4, 20], 5))
OUTPUT
4
30

The same pattern solves "minimum capacity to ship packages in D days", "split array largest sum", "square root of a number" and "aggressive cows".

Common bugs (and how to avoid them)

Bug What happens Fix
while low < high with high = mid - 1 Skips checking the last element Use low <= high with mid ± 1, or low < high with high = mid
low = mid instead of mid + 1 Infinite loop when two elements remain When the mid is ruled out, always move past it
low + high overflow Wrong mid in Java/C++ for huge arrays low + (high - low) / 2
Unsorted input Wrong answers, silently Sort first, or make sure the condition is monotonic
Off-by-one in the return Returns the insert position instead of -1 Decide clearly: exact match or lower bound
A safe template

Pick one template and use it always. Exact match: low=0, high=n-1, while low <= high, move mid±1. Boundary search (first true): low=0, high=n, while low < high, high = mid when the condition is true, otherwise low = mid + 1, return low.

Where binary search is used in real life

  • Database indexes (B-trees) use the same halving idea to find a row among millions.
  • git bisect finds the commit that introduced a bug by testing the middle commit each time.
  • Autocomplete finds the range of words starting with a prefix in a sorted list.
  • Machine learning: searching for a good threshold or learning rate when the metric changes in one direction.
  • Operating systems: finding free memory blocks and scheduling.
Key takeaways
  • Binary search finds a target in a sorted array by repeatedly checking the middle and discarding half.
  • Time O(log n): 1 billion items need only about 30 checks. Iterative space is O(1).
  • Use mid = low + (high - low) / 2 in Java and C++ to avoid overflow.
  • Lower bound finds the first value ≥ target; with it you can find first and last occurrences and insert positions.
  • Binary search on the answer works whenever "does value x work?" is monotonic.

Test yourself

Score: 0 / 5

1. What must be true about the data for binary search to work?

Binary search relies on knowing which half the target is in. Sorting guarantees that.

2. Roughly how many checks does binary search need for 1,000,000 items in the worst case?

2 to the power of 20 is about 1,048,576, so about 20 halvings.

3. Why use low + (high - low) / 2 instead of (low + high) / 2 in Java?

Both give the same middle mathematically, but the first form never creates a number larger than high.

4. arr = [2, 4, 4, 4, 9]. What does lower_bound(arr, 4) return?

Lower bound returns the first index whose value is at least 4, which is index 1.

5. "Find the minimum speed to finish in h hours" is solved with binary search over what?

This is "binary search on the answer". If speed k works, any faster speed also works, so the condition is monotonic.

Frequently asked questions

Is binary search faster than a hash table lookup?

No. A hash table lookup is O(1) on average, and binary search is O(log n). But binary search needs no extra memory, keeps the data in order, and can answer range questions ("all values between 40 and 60", "next bigger value") that a hash table cannot.

Can binary search work on a linked list?

Technically yes, but it is pointless. Finding the middle of a linked list takes O(n) time, so the total becomes O(n), no better than a linear scan. Binary search needs random access, such as an array.

What is the difference between bisect_left and bisect_right in Python?

For a value that appears several times, bisect_left returns the index of the first occurrence (lower bound) and bisect_right returns the index just after the last occurrence (upper bound). Their difference is the count of that value.

Iterative or recursive binary search, which is better?

Iterative is usually better: it uses O(1) extra space and has no recursion overhead. Recursive code can look cleaner and is fine for interviews, but uses O(log n) stack space.

What is ternary search?

Ternary search splits the range into three parts and is used to find the maximum or minimum of a function that increases and then decreases (unimodal). For searching a sorted array, binary search is faster than ternary search.

Prefer watching?

Real interview walkthroughs and AI explained simply on our YouTube channel.

Subscribe on YouTube