liveBook · Manning

Binary search

As explained in Grokking Algorithms, Second Edition

An algorithm that searches a sorted list by repeatedly narrowing the search interval and returns the position of a matching element or null when no match exists.

Looking up a word in a dictionary? You probably would not start at the first page and turn every page until you find it. Because the words are in order, you can open near the middle and decide which half to search next. Binary search uses that idea on a sorted list. It returns the position of a matching item, or null if the item is absent. section 1.3

Guess a number from 1 to 100

Guess 50 first. If it is too low, you can rule out 1 through 50. Guess 75 next. If that is too high, look between 50 and 75; a next guess is 63. Each answer removes about half the remaining possibilities. You need at most seven guesses, rather than up to 100 when guessing one number after another. section 1.3 section 1.4

To search an indexed, sorted list:

  • Set low and high to the positions of the first and last items. section 1.4
  • Check the middle item. If it matches, return its position. If it is too high, move high below the middle; if too low, move low above it. section 1.4
  • Repeat while low is no greater than high. If the bounds cross without a match, return null. section 1.4

The advantage grows with the list. Simple search may check every item: its running time is O(n). Binary search repeatedly halves the possibilities, giving O(log n) running time. A list of 4 billion items could take up to 4 billion checks with simple search, but at most 32 with binary search. These rates describe how work grows, not a fixed number of seconds. section 1.5 section 1.12

def binary_search(arr, item):
  low = 0                                   #1
  high = len(arr)—1                         #1

  while low <= high:                        #2
    mid = (low + high) // 2                 #3
    guess = arr[mid]
    if guess == item:                       #4
      return mid
    elif guess > item:                      #5
      high = mid - 1
    else:                                   #6
      low = mid + 1
  return None                               #7

my_list = [1, 3, 5, 7, 9]                   #8

print(binary_search(my_list, 3)) # => 1     #9
print(binary_search(my_list, -1)) # => None #10
#1 low and high keep track of which part of the list you’ll search in.
#2 While you haven’t narrowed it down to one element . . .
#3 . . . check the middle element.
#4 Found the item.
#5 The guess was too high.
#6 The guess was too low.
#7 The item doesn’t exist.
#8 Let’s test it!
#9 Remember, lists start at 0. The second slot has index 1.
#10 None means null in Python. It indicates that the item wasn’t found.

The Python implementation shows low and high tracking the remaining interval, a middle-index check on each pass, and None returned when the item is absent. section 1.4

How Grokking Algorithms, Second Edition covers it

How deep it goes
Works through successive midpoint guesses and gives a complete Python implementation with search bounds and a missing-item return.
Examples
Uses dictionary lookup, a 1-to-100 guessing game, and a Python list searched for present and absent values.
What it assumes
Requires a sorted list for the search and explains logarithms through repeated halving rather than assuming familiarity with them.
What you can do after
Build it from scratch using midpoint comparisons and low/high bounds, then compare its running time with simple search.
How it connects
Connects binary search to simple search, logarithms, and big O notation through their different running-time growth rates.

Also explained in

Compare how 7 books explain binary search →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Grokking Algorithms, Second Edition. The section links point to the parts of the book it draws on.