Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

Functions that call themselves (recursion)

After this lesson: I can write a function that calls itself.

50 min lesson3 commandsJupyterLab data science lab: pandas, Matplotlib and scikit-learn in the browser

Folders inside folders inside folders

Your phone wants to know how many photos are in a folder. But the folder holds other folders, and some of those hold more folders.

You cannot know how deep it goes. There is a neat answer: count the photos here, then ask the same question of each folder inside.

By the end of this lesson

Note

I can write a function that calls itself.

Key words

WordWhat it meansExample
Recursiona function calling itself to solve a smaller problem
Base casethe case answered straight away, which stops the calls
Recursive casethe case that calls the function again
Call stackthe stack of calls still waiting to finish
Stack overflowrunning out of room on the call stackRecursionError

The cells students fill in are left blank in this preview.

Every recursion needs a base case

A recursive function has two parts. The base case is small enough to answer at once, with no more calls.

The recursive case calls the same function on a smaller problem. Each call must move closer to the base case. A countdown from 3 calls itself with 2, then 1, then 0, and 0 is the base case.

A countdown that calls itself

Code: python
# A countdown that calls itself.
def countdown(n):
    if n == 0:
        print("Go!")
        return
    print(n)
    countdown(n - 1)

countdown(3)
# Change the 3 to 5. Then change n == 0 to n == 2.
# What does the second change do?

The call stack keeps each call waiting

When a function calls itself, the first call has not finished. It waits on the call stack, the same stack as in the last lesson.

Each new call is pushed on top. When the base case returns, the calls are popped one at a time, last in first out. Each one finishes its own work with the answer it was given.

Watch the call stack

Code: python
# Watch the call stack grow and shrink.
def factorial(n, depth):
    gap = "    " * depth
    print(gap + "call factorial(" + str(n) + ")")
    if n == 1:
        answer = 1
    else:
        answer = n * factorial(n - 1, depth + 1)
    print(gap + "factorial(" + str(n) + ") returns " + str(answer))
    return answer

factorial(4, 0)
# Change 4 to 6. How deep does the stack go now?

Worked example: factorial(4) unwinding

CallWaits forReturns
factorial(4)4 times factorial(3)24
factorial(3)3 times factorial(2)6
factorial(2)2 times factorial(1)2
factorial(1)Nothing: the base case1

The cells students fill in are left blank in this preview.

A common mistake

Warning

Many people think a recursive function stops by itself when the job is done. It stops only when it reaches a base case. With no base case, or a call that never gets smaller, it calls itself until the call stack runs out.

The call stack has a limit

Python allows about a thousand calls on the stack. After that it stops the program with a RecursionError, its name for a stack overflow.

The Run below times a function called down that calls itself with no base case. It never gets as far as a time. Read the last line it prints.

A function with no base case

Command
docker exec jupyter python3 -m timeit -s "def down(n):" -s "    return down(n - 1)" "down(5)"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Recursion can repeat work

A Fibonacci number is the sum of the two before it: 0, 1, 1, 2, 3, 5, 8. The neat recursive version calls itself twice each time.

So fib(5) works out fib(3) twice and fib(2) three times. The Runs time fib(20) and fib(25). Compare the two times, then count the calls in your notebook.

Time a recursive fib(20)

Command
docker exec jupyter python3 -m timeit -s "def fib(n):" -s "    return n if n < 2 else fib(n - 1) + fib(n - 2)" "fib(20)"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Time a recursive fib(25)

Command
docker exec jupyter python3 -m timeit -s "def fib(n):" -s "    return n if n < 2 else fib(n - 1) + fib(n - 2)" "fib(25)"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Type this in your notebook: count the calls

Code: python
calls = 0

def fib(n):
    global calls
    calls = calls + 1
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

for n in [10, 15, 20, 25]:
    calls = 0
    print("fib(" + str(n) + ") =", fib(n), "after", calls, "calls")

Going further: merge sort by recursion

Merge sort is recursive at heart. A list of 0 or 1 items is the base case. Otherwise, split it in half, merge sort each half, and merge the two answers.

Write it in your notebook using your merge from lesson 2. Check it against sorted on 1,000 random numbers.

About this lab and its source: JupyterLab data science lab: pandas, Matplotlib and scikit-learn in the browser

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.