The Lost Letter — A Story About Searching
Friend A picks up pages one at a time, reads each number, and keeps going until they hit 487. If they're unlucky, that's 1,000 checks.
Friend B first stacks the pages in order, opens to the middle, sees “500 — too high”, throws away the top half, opens the middle of what's left, and repeats. Friend B finds page 487 in about 10 checks.
Friend A is Linear Search. Friend B is Binary Search. Same goal, wildly different effort — and the difference is the whole point of this lesson.
Searching is everywhere: looking up a contact, finding a record in a database, hitting Ctrl‑F. The smartest algorithm depends on one question: is your data already sorted? If not, linear search is your tool. If yes, binary search is dramatically faster.
Linear Search — What It Is & Where It Shines
Linear Search (also called sequential search) is the most intuitive search there is: start at the first element, compare it to your target, and walk forward one element at a time until you either find a match or run off the end of the list. It makes no assumptions about the data — the list can be in any order at all.
Hands‑On: Watch Linear Search Run, Line by Line
Press Step to advance one operation at a time, or Auto‑Play to watch it run. The highlighted code line on the right is the exact line being executed, and the status bar explains what that line is doing right now. Try the different target chips — including one that's not in the list.
Every element you pass turns faded — it has been checked and ruled out. For target 99 (last element) linear search must touch every cell. For a missing target like 77 it scans the whole list and returns -1. That worst‑case “check everything” behaviour is exactly what makes it O(n).
What Each Line Is Doing
This table is the same mapping the animation uses — each code line and the task it performs.
The Full Python Code
def linear_search(arr, target):
# Walk through every index, left to right
for i in range(len(arr)):
if arr[i] == target: # compare current element with target
return i # found it -> return its position
return -1 # scanned everything, not found
# --- Try it out ---
data = [42, 17, 8, 99, 23, 4, 56, 31]
print(linear_search(data, 23)) # 23 sits at index 4
print(linear_search(data, 77)) # 77 is missing
Linear Search — Cost Summary
| Scenario | Comparisons | Big‑O | Meaning |
|---|---|---|---|
| Best case | 1 | O(1) | Target is the very first element. |
| Average case | n / 2 | O(n) | Target is somewhere in the middle. |
| Worst case | n | O(n) | Target is last, or not present at all. |
| Extra memory | — | O(1) | No extra space needed. |
| Data requirement | — | None | Works on sorted or unsorted data. |
Binary Search — The Power of Halving
Binary Search needs one thing in return for its speed: the data must be sorted. Given that, it looks at the middle element, decides whether the target is in the left half or the right half, and throws the other half away — every single step.
Binary search is only correct on sorted data. Run it on an unsorted list and it will confidently return the wrong answer. If your data isn't sorted and you only search once, sorting first (O(n log n)) may cost more than a single linear scan (O(n)) — so choose deliberately.
Hands‑On: Watch Binary Search Halve the List
Same controls as before. Watch how the lo and hi pointers close in, the mid element lights up blue, and half the array greys out after every single comparison. Notice how few steps it takes compared with linear search above.
What Each Line Is Doing
The Full Python Code
def binary_search(arr, target):
lo, hi = 0, len(arr) - 1 # search window = whole array
while lo <= hi:
mid = (lo + hi) // 2 # middle of the window
if arr[mid] == target:
return mid # found
elif arr[mid] < target:
lo = mid + 1 # go right
else:
hi = mid - 1 # go left
return -1 # not found
# --- Try it out (NOTE: list must be sorted) ---
data = [4, 8, 17, 23, 31, 42, 56, 99]
print(binary_search(data, 23)) # 23 sits at index 3
print(binary_search(data, 77)) # 77 is missing
Python ships with the bisect module — bisect.bisect_left(arr, target) gives you a production‑grade binary search for finding insertion points. Understand the mechanics first, then reach for the battle‑tested library in real code.
Visual Diagram — How Binary Search Eliminates Half Each Time
Ask: “how many times can I halve n before I reach 1?” That count is log₂(n). For n = 1,000,000 the answer is ~20. For n = 1,000,000,000 it's only ~30. Each doubling of the data adds just one step — that is the magic of logarithmic growth.
Head‑to‑Head Comparison
| Property | Linear Search | Binary Search |
|---|---|---|
| Data must be sorted? | No | Yes — mandatory |
| Time complexity (worst) | O(n) | O(log n) |
| Best case | O(1) | O(1) |
| Checks for 1,000,000 items | up to 1,000,000 | ~20 |
| Extra memory | O(1) | O(1) iterative |
| Code complexity | Very simple | Easy to get off‑by‑one wrong |
| Works on linked lists? | Yes | No (needs random access) |
| Best when… | Small or unsorted data | Large, sorted, frequently searched data |
| Step | Checks element |
|---|---|
| 1 | index 0 |
| 2 | index 1 |
| … | … |
| 8 | index 7 |
| Total | 8 comparisons |
| Step | Window size |
|---|---|
| 1 | 8 → 4 |
| 2 | 4 → 2 |
| 3 | 2 → 1 |
| Total | 3 comparisons |
Golden Rules
lo <= hi, not <) and the updates
(mid + 1 / mid - 1). Get these exactly right.
bisect, C++'s
std::lower_bound, Java's Arrays.binarySearch — learn the
mechanics, then trust the tested implementation in production.