One natural way to use recursion is to rewrite binary search.

Recall that our iterative implementation of binary search looked like this:

def binarySearch(item, lst):
    """
    inputs: an item and a list of items
    output: Boolean value (True if the item is in the list, False otherwise)
    """
    low = 0
    high = len(lst)-1
    while low <= high: 
        mid = (low + high) // 2
        if item == lst[mid]:
            return True
        elif item < lst[mid]:
            high = mid - 1
        elif item > lst[mid]:
            low = mid + 1
    return False

This code is pretty complicated, and we had to plan very carefully to make sure that the while loop works the way we intended.

But binary search seems to lend itself to recursion --- our idea was "check the middle element, then update low or high and binary search again". This notion of "just binary search again, but on a smaller list" will make a recursive version much cleaner to read.

How will we pass around a "smaller" list? One way is to use a helper function. We can add the parameters low and high, since those are what we were tracking in the loop of the iterative version.

def binarySearchHelper(item, lst, low, high):
    """
    input: an item, a list of items, and two indices in the list
    output: Boolean value (True if the item is in the list, False otherwise)
    """
    if low > high:
        return False
    else:
        mid = (low + high) // 2
        if item == lst[mid]:
            return True
        elif item < lst[mid]:
            return binarySearchHelper(item, lst, low, mid-1)
        elif item > lst[mid]:
            return binarySearchHelper(item, lst, mid+1, high)

Notice that the condition from the while loop has now become our base case: it tells us when to stop doing binary search (either to stop looping or to stop making recursive calls).

Now the only thing we need to do is figure out how to start a binary search. At the beginning, we know that low and high should have us search the entire list, so the implementation of binarySearch is simply:

def binarySearch(item, lst):
    return binarySearchHelper(item, lst, 0, len(lst)-1)

To see how this is working, let's add a print statement at the beginning of the binarySearchHelper function that prints low and high. Now when we run on an example list like [-20, -12, -4, 1, 7, 44, 45, 46, 58, 67, 99, 145] and search for 7, we get:

low:  0  high: 11
low:  0  high: 4
low:  3  high: 4
low:  4  high: 4

... and then the search returns True, because it finds that 7 is in position 4 of the list.

Another example, searching for 140 in the list:

low:  0  high: 11
low:  6  high: 11
low:  9  high: 11
low: 11  high: 11
low: 11  high: 10

... and then the search returns False, because low is greater than high.

Try this out for yourself!


Complete code of my testing file:

def binarySearchHelper(item, lst, low, high):
    """
    input: an item, a list of items, and two indices in the list
    output: Boolean value (True if the item is in the list, False otherwise)
    """
    print("low: %2d  high: %d" % (low, high))
    if low > high:
        return False
    else:
        mid = (low + high) // 2
        if item == lst[mid]:
            return True
        elif item < lst[mid]:
            return binarySearchHelper(item, lst, low, mid-1)
        elif item > lst[mid]:
            return binarySearchHelper(item, lst, mid+1, high)

def binarySearch(item, lst):
    """
    inputs: an item and a list of items
    output: Boolean value (True if the item is in the list, False otherwise)
    """
    return binarySearchHelper(item, lst, 0, len(lst)-1)
        
def main():
    myList = [-20, -12, -4, 1, 7, 44, 45, 46, 58, 67, 99, 145]

    searchItem = int(input("What number do you want to find? "))

    result = binarySearch(searchItem, myList)

    print(result)

main()