Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 31

Binary Search: Implementation and Complexity Analysis

Recommended resources

Introduction

Welcome back! In our last lesson, we explored linear search, a simple method for finding an item in a list. We saw that its performance, with a time complexity of O(n), is directly tied to the size of the list. This is fine for small lists, but it becomes very slow for large ones.

Today, we'll look at a much more powerful and efficient algorithm: binary search. As previewed, this algorithm requires one important condition: the list must be sorted. By leveraging this sorted property, binary search can find an element in a tiny fraction of the time it would take linear search.

Your goal for this lesson is to implement binary search on a sorted list and analyze its O(log n) time complexity. We will cover the "divide and conquer" strategy it uses, write the Python code for it, and then dive into what O(log n) means and why it's so incredibly fast.


1. The Core Idea: Divide and Conquer

Instead of checking every single element, what if we could eliminate half of the remaining elements with a single check? This is the central idea of binary search.

Imagine you're looking for a word in a physical dictionary. You don't start at "A" and read every word. You open it somewhere in the middle. If your word comes alphabetically before the words on that page, you know your word is in the first half of the dictionary. If it comes after, you know it's in the second half. You've just eliminated half the dictionary in one step. You can then repeat this process on the remaining half.

This strategy has a name: divide and conquer. Let's explore this with a non-technical analogy.

How to find things (an intro to binary search)

This article introduces binary search using a clever story about finding a stolen bike on CCTV footage. It does an excellent job of explaining the core 'halving' principle without any code.

Read the first part of the article, starting from the top down to just before the line: 'Now, if you’re a programmer there are two things you should probably have noticed...'. Focus on how checking the midpoint repeatedly cuts the search time in half.

As the article explains, you repeatedly divide the problem space (the time the bike could have been stolen, or the part of the list where an element could be) in two.

Let's see how this applies to a sorted list of numbers. The "middle of the dictionary" becomes the middle element of our list.

Binary Search for an Absent Element
This diagram shows a binary search for the number 24. The algorithm starts with the full sorted list, checks the middle element (16), realizes 24 must be in the right half, and discards the left. It repeats this process, narrowing the blue search area until it concludes the number isn't there.

2. Implementing Binary Search in Python

Now that we have the intuition, let's translate this "divide and conquer" logic into Python code. We will implement an iterative version of binary search using a while loop.

We'll need to keep track of three things:

  1. A low pointer to mark the beginning of our current search space.
  2. A high pointer to mark the end.
  3. A mid pointer that we calculate in each step.

This video provides an excellent walkthrough of the core concept and a clear, step-by-step implementation.

Binary Search Algorithm Explained (Full Code Included) - Python Algorithms Series for Beginners

This video from Derrick Sherrill begins with a great animation of binary search, then walks through building the Python code for it line by line.

First, watch the introductory animation (0:07 - 0:58) to see the algorithm in action. Then, watch the main coding segment (0:58 - 4:56) where the iterative function is built and explained. Don't worry about memorizing the code; focus on understanding the role of the begin_index, end_index, and midpoint variables.

Let's review the code from the video. It's a standard and effective implementation of binary search.

def binary_search(sequence, item):



    # Set the boundaries of the search space
    begin_index = 0
    end_index = len(sequence) - 1




    # Loop as long as the search space is valid
    while begin_index <= end_index:



        # Calculate the midpoint index (using floor division //)
        midpoint = begin_index + (end_index - begin_index) // 2
        midpoint_value = sequence[midpoint]




        # Compare the midpoint value with the item we're looking for
        if midpoint_value == item:



            # Found it! Return the index.
            return midpoint
        elif item < midpoint_value:



            # The item must be in the left half
            end_index = midpoint - 1
        else:



            # The item must be in the right half
            begin_index = midpoint + 1
    



    # If the loop finishes, the item was not in the sequence
    return None




# --- Let's test it! ---
sorted_list = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]




# Case 1: Target is in the list
target1 = 23
result1 = binary_search(sorted_list, target1)
print(f"Searching for {target1}: Found at index {result1}")




# Case 2: Target is not in the list
target2 = 50
result2 = binary_search(sorted_list, target2)
if result2 is not None:
    print(f"Searching for {target2}: Found at index {result2}")
else:
    print(f"Searching for {target2}: Not found in the list.")

Your Turn: Practice with the Code

  1. Copy the binary_search function and the test cases into a Python file.
  2. Run the code to confirm you get the same output.
  3. Add test cases to search for:
    • The very first element (2).
    • The very last element (91).
    • An element smaller than any in the list (e.g., 1).
  4. Before running your new tests, trace the values of begin_index, end_index, and midpoint on paper for one of the cases. This will solidify your understanding of how the search space shrinks.

3. Analyzing Time Complexity: What is O(log n)?

We know binary search is fast, but how fast? Its time complexity is O(log n) (read "O of log n"). To understand this, we first need a clear, intuitive grasp of what a logarithm represents in computer science.

Your math background with exponents will be very helpful here. A logarithm is the inverse of an exponent. The expression asks the question: "How many times must I multiply 2 by itself to get n?"

In algorithms, we can flip this question around: "Starting with n, how many times can I divide it by 2 until I get down to 1?" This is exactly what binary search does to its search space.

This first video gives a great explanation.

Deeply Understanding Logarithms In Time Complexities & Their Role In Computer Science

This clip from Back To Back SWE explains what a logarithm is from a computer science perspective, focusing on the concept of 'halving'.

Watch from the beginning to 3:21. The key takeaway is the connection between log base 2 and the action of repeatedly cutting something in half.

Now, let's connect that concept directly to our binary search algorithm.

Deeply Understanding Logarithms In Time Complexities & Their Role In Computer Science

In this next clip from the same video, the presenter applies the logarithm concept directly to a binary search example, showing how the number of 'halving' operations is log n.

Watch from 7:02 to 9:46. Notice how the worst-case number of checks corresponds to log of the array's size.

To formalize this, consider an array of n elements.

  • After 1 comparison, the search space is at most n / 2.
  • After 2 comparisons, it's at most n / 4.
  • After k comparisons, it's at most n / 2^k.

The search ends when the search space has size 1. So, in the worst case, we are looking for the number of steps, k, where n / 2^k = 1. Solving for k gives us n = 2^k, which means k = log₂(n). This is why the time complexity is O(log n).

This article section provides a concise summary of this mathematical reasoning.

Binary Search – Algorithm and Time Complexity Explained

This resource from freeCodeCamp clearly lays out the math behind the O(log n) complexity of binary search.

Read the section titled 'What Is the Time Complexity of Binary Search?'. Pay close attention to the formula and the final comparison between linear and binary search for 1000 elements.

The difference between O(n) and O(log n) is not just a small improvement; it's monumental for large inputs.

Time Complexity Comparison: Linear Search vs. Binary Search
This graph visually compares the growth of linear search (`O(n)`) and binary search (`O(log n)`). As the number of values (`n`) increases, the time for linear search grows proportionally, while the time for binary search barely increases, demonstrating its incredible efficiency.

For a list with a million items, linear search might take a million steps. Binary search would take only about 20 steps (). This is the power of a better algorithm.


Conclusion

Fantastic work today! You've tackled one of the most important and elegant algorithms in computer science.

Key Takeaways:

  • Binary search operates on sorted data only.
  • It uses a divide and conquer approach, repeatedly halving the search space with each comparison.
  • The iterative implementation uses low, high, and mid pointers to manage the search space within a while loop.
  • The time complexity is O(log n), which is exceptionally fast because the number of operations grows very slowly as the input size n increases.

Preview of the Next Lesson:
We've now seen how efficient searching can be if a list is sorted. But what if our data isn't sorted to begin with? In the next lesson, we'll shift our focus from searching to sorting and implement our first sorting algorithm, insertion sort. We'll see how it works and analyze its O(n²) time complexity.