04. Searching Algorithms — Linear Search & Binary Search
1516 words
8 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# 04. Searching Algorithms — Linear Search & Binary Search > **What problem does this solve?** Given a list of items, find whether a specific value exists in it, and if so, at what position.

04. Searching Algorithms — Linear Search & Binary Search
What problem does this solve? Given a list of items, find whether a specific value exists in it, and if so, at what position. This is the most fundamental algorithmic operation — every program that looks up data needs search.
1. Linear Search (Unordered Data)
Mental Model
You're looking for a specific exam paper in an unsorted pile. You check each paper one by one until you find it or reach the bottom of the pile.
How It Works
(Diagram)
Implementation
python# runnable def linear_search(arr, target): """Return index of target in arr, or -1 if not found. Time: O(n) — must check every element in worst case Space: O(1) — no extra memory needed """ for i in range(len(arr)): if arr[i] == target: return i # Found at index i return -1 # Not found # Test arr = [64, 34, 25, 12, 22, 11, 90] print(f"Search 12: found at index {linear_search(arr, 12)}") # 3 print(f"Search 99: found at index {linear_search(arr, 99)}") # -1
Complexity Analysis
| Case | When | Comparisons | Complexity |
|---|---|---|---|
| Best | Target is first element | 1 | (O(1)) |
| Average | Target is in the middle | (n/2) | (O(n)) |
| Worst | Target not present | (n) | (O(n)) |
2. Binary Search (Sorted Data)
Mental Model
You're looking up a word in a dictionary. You don't start at page 1 and scan — you open to the middle. If the word is alphabetically before that page, you search the first half; otherwise, the second half. Each step cuts the search space in half.
How It Works
(Diagram)
Implementation (Iterative)
python# runnable def binary_search_iterative(arr, target): """Return index of target in sorted arr, or -1 if not found. Time: O(log n) — search space halves each iteration Space: O(1) """ left, right = 0, len(arr) - 1 while left <= right: mid = (left + right) // 2 if arr[mid] == target: return mid elif arr[mid] < target: left = mid + 1 # Search right half else: right = mid - 1 # Search left half return -1 # Test sorted_arr = [11, 12, 22, 25, 34, 64, 90] print(f"Search 25: index {binary_search_iterative(sorted_arr, 25)}") # 3 print(f"Search 99: index {binary_search_iterative(sorted_arr, 99)}") # -1
Implementation (Recursive)
python# runnable def binary_search_recursive(arr, target, left=None, right=None): """Recursive binary search.""" if left is None: left, right = 0, len(arr) - 1 if left > right: return -1 mid = (left + right) // 2 if arr[mid] == target: return mid elif arr[mid] < target: return binary_search_recursive(arr, target, mid + 1, right) else: return binary_search_recursive(arr, target, left, mid - 1) print(binary_search_recursive(sorted_arr, 34)) # 4
Step-by-Step Trace
Searching for 34 in [11, 12, 22, 25, 34, 64, 90]:
| Step | left | right | mid | arr[mid] | Comparison | Action |
|---|---|---|---|---|---|---|
| 1 | 0 | 6 | 3 | 25 | 25 < 34 | Search right |
| 2 | 4 | 6 | 5 | 64 | 64 > 34 | Search left |
| 3 | 4 | 4 | 4 | 34 | 34 == 34 | Found! |
Searching for 99 in [11, 12, 22, 25, 34, 64, 90]:
| Step | left | right | mid | arr[mid] | Comparison | Action |
|---|---|---|---|---|---|---|
| 1 | 0 | 6 | 3 | 25 | 25 < 99 | right |
| 2 | 4 | 6 | 5 | 64 | 64 < 99 | right |
| 3 | 6 | 6 | 6 | 90 | 90 < 99 | right |
| 4 | 7 | 6 | — | — | — | left > right → not found |
Finding First/Last Occurrence of Duplicates
python# runnable def first_occurrence(arr, target): """Find first index of target in sorted array with duplicates.""" left, right = 0, len(arr) - 1 result = -1 while left <= right: mid = (left + right) // 2 if arr[mid] == target: result = mid # Record this occurrence right = mid - 1 # But keep searching left elif arr[mid] < target: left = mid + 1 else: right = mid - 1 return result def last_occurrence(arr, target): """Find last index of target in sorted array with duplicates.""" left, right = 0, len(arr) - 1 result = -1 while left <= right: mid = (left + right) // 2 if arr[mid] == target: result = mid # Record this occurrence left = mid + 1 # But keep searching right elif arr[mid] < target: left = mid + 1 else: right = mid - 1 return result # Test arr = [1, 2, 3, 3, 3, 3, 4, 5] print(f"First 3: {first_occurrence(arr, 3)}") # 2 print(f"Last 3: {last_occurrence(arr, 3)}") # 5 print(f"Count of 3: {last_occurrence(arr, 3) - first_occurrence(arr, 3) + 1}") # 4
3. Comparison Table
| Feature | Linear Search | Binary Search |
|---|---|---|
| Data requirement | Any list | Sorted list |
| Best case | (O(1)) — first element | (O(1)) — middle element |
| Average case | (O(n)) | (O(\log n)) |
| Worst case | (O(n)) | (O(\log n)) |
| Space | (O(1)) | (O(1)) iterative, (O(\log n)) recursive |
| Number of comparisons (n=1000) | Up to 1000 | At most 10 |
| Stable | Yes | Yes |
| Can find first/last occurrence | Yes (linear scan) | Yes (modified binary search) |
4. Common Bugs & Pitfalls
python# BUG 1: Integer overflow (not in Python, but in languages with fixed-width ints) # Python handles arbitrary precision, but the formula matters: mid = (left + right) // 2 # Fine in Python # Safer (for other languages): mid = left + (right - left) // 2 # Avoids overflow # BUG 2: Off-by-one in bounds def buggy_bs(arr, target): left, right = 0, len(arr) # Bug: should be len(arr) - 1 while left < right: # Bug: should be <= mid = (left + right) // 2 if arr[mid] == target: return mid elif arr[mid] < target: left = mid # Bug: should be mid + 1 else: right = mid # Bug: should be mid - 1 return -1 # BUG 3: Not checking if the list is sorted # Binary search on unsorted data will give wrong results binary_search_iterative([3, 1, 4, 1, 5, 9], 4) # May return -1 or wrong index # BUG 4: Infinite loop with adjacent elements def infinite_bs(arr, target): left, right = 0, len(arr) - 1 while left <= right: mid = left + (right - left) // 2 if arr[mid] == target: return mid elif arr[mid] < target: left = mid # Should be mid + 1 else: right = mid # Should be mid - 1 return -1 # When left=0, right=1, mid=0: # If target > arr[0], left becomes 0 again → infinite loop!
5. Practice Questions
Q1. Trace binary search for finding 7 in [1, 3, 5, 7, 9, 11, 13, 15]. Show left, right, mid values at each step.
Q2. Write a function
count_occurrences(arr, target) that counts how many times target appears in a sorted array, using binary search. Complexity should be O(log n).
Q3. What is the minimum number of comparisons needed to find a value in a sorted array of 1 million elements?
Q4. Modify binary search to find the peek element in a bitonic array (increasing then decreasing). Example: [1, 3, 8, 12, 4, 2] → peek = 12 at index 3.
Q5. Linear search on average compares n/2 elements. True or false? Justify.
Q6. Can you use binary search on a linked list? Why or why not?
Q7. You have an array of unknown length (in a language where accessing out-of-bounds throws). How would you find the length to then binary search?
Q8. What is the recurrence for binary search? Solve it.
Q9. Write a function that uses binary search to find the square root of an integer (floor) without using math.sqrt.
Q10. You have 1000 names sorted alphabetically. Binary search takes at most 10 comparisons. What if the list has 1,000,000 names?AnswersA1.
| Step | left | right | mid | arr[mid] | Action |
|---|---|---|---|---|---|
| 1 | 0 | 7 | 3 | 7 | Found! |
Only 1 comparison in the best case when target is the middle element.A2.pythondef count_occurrences(arr, target): first = first_occurrence(arr, target) if first == -1: return 0 last = last_occurrence(arr, target) return last - first + 1A3. (\log_2(1,000,000) \approx 20) comparisons (since (2^{20} = 1,048,576)).A4.pythondef find_peek(arr): left, right = 0, len(arr) - 1 while left < right: mid = (left + right) // 2 if arr[mid] > arr[mid + 1]: right = mid else: left = mid + 1 return leftA5. True. In the average case, target is found at position n/2 after n/2 comparisons. If target isn't present, all n elements are compared.A6. No. Linked lists don't have O(1) random access — finding the middle element requires O(n) traversal, eliminating the benefit of halving.A7. Use exponential search: check indices 1, 2, 4, 8, 16, ... until out-of-bounds, then binary search between last valid index and the overflow index.A8. (T(n) = T(n/2) + 1). Unwinding: (T(n) = T(n/2^k) + k). When (n/2^k = 1), (k = \log n), so (T(n) = T(1) + \log n = O(\log n)).A9.pythondef sqrt_floor(n): if n < 2: return n left, right = 1, n // 2 while left <= right: mid = (left + right) // 2 sq = mid * mid if sq == n: return mid elif sq < n: left = mid + 1 else: right = mid - 1 return rightA10. (\log_2(1,000,000) \approx 20). Adding 900,000 names only adds ~10 comparisons. This demonstrates the power of logarithmic growth. Join Discord Previous03. Complexity Analysis — Recurrence Relations & Master TheoremNext05. Selection Sort & Insertion Sort