Skip to Content
Course content

407: Implementing Binary Search

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're building a system to manage a massive database of user IDs—let's say ten million of them—and they're already sorted in ascending order. You need to find the index of one specific ID. Now, the most intuitive way to do this is the way most of us start: just start at the beginning and check every single element until you find a match. It's simple, it's readable, and it works. But in a production environment, "it works" isn't always enough.

The cost of scanning everything

If we take the linear approach, we're looking at a worst-case scenario where the ID we want is at the very end of the list, or not there at all. In Python, that looks like a simple for loop. While the code is clean, the performance cost is linear. If the list grows from ten million to a hundred million, your search time grows by a factor of ten. I've seen this cause massive latency spikes in systems where a developer assumed the list "wouldn't get that big."

def linear_search(sorted_list, target):
    for index, value in enumerate(sorted_list):
        if value == target:
            return index
    return -1

The problem here is that we're ignoring the most valuable piece of information we have: the list is already sorted. Linear search treats a sorted list exactly the same as a jumbled mess. It's a waste of the work that went into sorting the data.

Cutting the search space in half

This is where binary search comes in. Instead of stepping through the list one by one, we jump straight to the middle. If the middle element is our target, we're done. If it's too high, we know our target must be in the left half. If it's too low, it's in the right half. We've just eliminated half of the remaining possibilities in a single operation. I like to think of it like searching for a word in a physical dictionary; you don't start at page one, you flip to the middle and pivot.

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

    while low <= high:
        mid = (low + high) // 2
        guess = sorted_list[mid]

        if guess == target:
            return mid
        if guess > target:
            high = mid - 1
        else:
            low = mid + 1
            
    return -1

Notice the use of // for floor division. In Python, we need an integer for our index, and a standard division would give us a float, which would crash the program the moment we tried to access the list. Also, pay close attention to mid - 1 and mid + 1. If we don't shift the boundaries past the middle element we just checked, we risk getting stuck in an infinite loop when low and high are right next to each other.

The sorting tax

You might be wondering why we don't use binary search for everything. The trade-off is the "sorting tax." Binary search is incredibly fast—logarithmic time, to be precise—meaning searching ten million items takes about 24 comparisons instead of ten million. However, this only works if the data is sorted. If your data changes constantly, the cost of re-sorting the list every time you add a new item will far outweigh the speed gains of the search itself.

As a rule of thumb: if you have a dataset that is written once and read many times, sort it once and use binary search. If your data is a chaotic stream of updates, stick to a linear search or, better yet, look into using a hash map (Python's dict) for O(1) lookup times.




📋 Practical Task

Implementing a Sorted Log Timestamp Finder

You are tasked with building a tool that searches through a sorted list of Unix timestamps from a server log. The timestamps are sorted in ascending order. Write a function called find_log_index that takes a list of timestamps and a target timestamp as arguments. The function must use the binary search algorithm to return the index of the target timestamp, or -1 if the timestamp does not exist in the logs.

Test your implementation with the following:

logs = [1610000000, 1610000500, 1610001000, 1610001500, 1610002000]
target = 1610001500
# Expected result: 3
Rating
0 0

There are no comments for now.

to be the first to leave a comment.