Python Arena
Step 1 of 4

A function that calls itself

You know that a function can call another function. A function can also call itself. That's called recursion, and it's another way to repeat something.

Just as a while loop needs a way out, a recursive function needs a way to stop. This one calls itself every time, so it never ends on its own:

def count_up(n):
    count_up(n + 1)


count_up(1)
Python stops with
Traceback (most recent call last):
  File "main.py", line 5, in <module>
    count_up(1)
    ~~~~~~~~^^^
  File "main.py", line 2, in count_up
    count_up(n + 1)
    ~~~~~~~~^^^^^^^
  File "main.py", line 2, in count_up
    count_up(n + 1)
    ~~~~~~~~^^^^^^^
  File "main.py", line 2, in count_up
    count_up(n + 1)
    ~~~~~~~~^^^^^^^
  [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded

Every call starts a new call before it has finished. Python keeps track of all the unfinished calls, and when there are about a thousand of them, it gives up with a RecursionError. The traceback shows the same line a few times and then says how many more times it was repeated.

The base case

A recursive function that works has two parts:

  • A base case: a situation so simple that the function handles it without calling itself. That's where the calls stop.
  • A recursive case: the function calls itself with a smaller problem, one step closer to the base case.
def countdown(n):
    if n == 0:
        print("Liftoff!")
    else:
        print(n)
        countdown(n - 1)


countdown(3)
Output
3
2
1
Liftoff!

countdown(3) prints 3 and calls countdown(2), which prints 2 and calls countdown(1), which prints 1 and calls countdown(0). That's the base case: it prints Liftoff! and doesn't call itself, so the calls end there. Each call has its own n: while countdown(2) runs, the n of countdown(3) is still 3.

Each call makes n one smaller, so it gets to 0 sooner or later. But what about countdown(-1)? Then n goes -2, -3, -4 and never hits 0, and the program ends in a RecursionError. The base case n <= 0 would catch that too.

Recursion that changes a list

A recursive function can also change a list. None of the calls below gets a list of its own: in every call, numbers is another name for powers, the one list of the main program, because a function gets the list itself and not a copy (part 5). So each call finds the numbers that the calls before it appended:

def keep_doubling(numbers, limit):
    if numbers[-1] * 2 <= limit:
        numbers.append(numbers[-1] * 2)
        keep_doubling(numbers, limit)


powers = [1]
keep_doubling(powers, 100)
print(powers)
Output
[1, 2, 4, 8, 16, 32, 64]

Here the base case is hidden: when the next number would go over the limit, the condition is false and the function ends without calling itself. A while loop does the same job:

def keep_doubling(numbers, limit):
    while numbers[-1] * 2 <= limit:
        numbers.append(numbers[-1] * 2)

A solution that repeats with a loop is called iterative, and one where a function calls itself recursive. Here the iterative version is shorter and easier to follow, so it's the better choice. Recursion earns its place when a problem breaks into smaller problems of the same kind: the factorial and binary search in the next steps, and the trees of the next module.

Your task

Challenge 1 of 3

Fill in the blanks: the base case's condition, and the argument of the recursive call. The program should print:

4
3
2
1
Go!

Challenge 2

What does this program print? Type each line it prints.

Challenge 3

A sensor writes its readings into a list, and when it fails, it writes negative numbers. Write a recursive function trim_end(readings) that removes the negative numbers from the end of the list, so that the list ends with a number that is 0 or more. Negative numbers earlier in the list stay where they are, and a list of only negative numbers ends up empty. The function changes the list it gets and returns nothing. Use recursion, not a loop.

For example, this program:

readings = [12, -3, 7, 0, -1, -5]
trim_end(readings)
print(readings)

prints:

[12, -3, 7, 0]