Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
407: Implementing Binary Search
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: 3There are no comments for now.