Python: Recursion

Recursion in Python is when a function calls itself to solve smaller parts of a bigger problem. This technique is useful when a task can be broken into simpler sub-tasks that look just like the original—like counting backwards, breaking down a number, or exploring nested lists and trees. It helps you solve problems by repeating the same pattern over and over. In this guide, we’ll learn how to write recursive functions using clear and fun examples, so you can understand how recursion works and start using it in your own Python projects.

Your First Recursive Function

Let’s start with a simple recursive function that counts down from a given number to zero. This shows the two most important parts of any recursive function: the base case, which stops the recursion, and the recursive case, where the function calls itself with a smaller input.

def countdown(n):

    # Base case: when n is 0, stop recursion
    if n == 0:
        print("Liftoff!")

    else:

        # Print the current number
        print(n)

        # Recursive case: call countdown with n - 1
        countdown(n - 1)


# Start the countdown from 5
countdown(5)

This is the basic structure you’ll use again and again: check if you’ve reached the end (base case), and if not, call the function again with updated input.

Recursion vs Loops

In Python, many tasks can be done using either a loop or a recursive function. Let’s take the simple example of printing numbers from 1 to 5.

With a loop, the code is straightforward:

# Loop version: prints 1 to 5
for i in range(1, 6):
    print(i)

Now, here’s how you can do the same thing with recursion:

def count_up(n, max):

    # Base case: stop when n goes past max
    if n > max:
        return

    # Print current number
    print(n)

    # Recursive call: count up from the next number
    count_up(n + 1, max)


# Start counting from 1 to 5
count_up(1, 5)

Both approaches print the same result, but recursion gives us a different way of thinking—breaking down a task into smaller versions of itself. While loops repeat by structure, recursion repeats by calling itself until it hits a stopping point (called the base case).

Note: Loops are usually easier to read and better for simple, repeating tasks. Recursion shines when a problem naturally breaks down into smaller parts—like trees, nested data, or mathematical puzzles. If both work, loops are often the safer and more memory-efficient choice. But recursion can feel more elegant for the right kind of task.

Returning Values with Recursion

In this example, the recursive function sum_numbers calculates the sum of all numbers from 1 up to n. It uses a base case when n is 1 to stop the recursion, then adds n to the result of the smaller problem (sum_numbers(n - 1)). This way, the function builds the final result by returning values back up the call stack.

def sum_numbers(n):

    if n == 1:  # Base case: stop recursion when n is 1
        return 1

    return n + sum_numbers(n - 1)  # Recursive case: add current n to sum of smaller numbers


print(sum_numbers(5))  # Output: 15

Here, each recursive call waits for the next call to return its value. When the base case is reached, the function returns 1. Then, each waiting call adds its current number n to that returned value, passing the total back up. This process continues until the first call returns the final sum. This shows how recursion can build results step-by-step while moving back through the calls.

Factorial Example

The factorial of a number n is the product of all positive integers up to n. This classic example shows how recursion breaks down the problem: the factorial of n is n multiplied by the factorial of n - 1. The base case stops the recursion when n reaches 0, returning 1.

def factorial(n):

    if n == 0:  # Base case: factorial of 0 is 1
        return 1

    return n * factorial(n - 1)  # Recursive case: n * factorial of (n - 1)


print(factorial(5))  # Output: 120

Each call waits for the next smaller factorial to be calculated. When the base case returns 1, the calls multiply their current number by the returned result, building the final answer step-by-step. This example perfectly shows how recursion simplifies repetitive calculations.

Fun with Fibonacci

The Fibonacci sequence is a famous series where each number is the sum of the two before it. This simple recursive function shows how each Fibonacci number calls the two previous ones until it reaches the base cases 0 or 1.

def fib(n):

    if n <= 1:  # Base cases: fib(0) = 0, fib(1) = 1
        return n

    return fib(n - 1) + fib(n - 2)  # Recursive case: sum of the two previous numbers


for i in range(10):
    print(f"fib({i}) = {fib(i)}")

This example helps you see how recursion can solve problems where the current result depends on multiple previous calls. Keeping the numbers small keeps it fun and easy to follow.

Working with Strings Recursively

Recursion isn’t just for numbers — it works great with strings too! Here’s a simple example that reverses a word by taking the first letter off and putting it at the end after reversing the rest.

def reverse(word):

    if word == "":  # Base case: empty string returns itself
        return word

    return reverse(word[1:]) + word[0]  # Recursive step: reverse the rest + first letter


print(reverse("zebra"))  # Output: arbez

This shows how recursion can process each part of a string step by step until everything is reversed. It’s a neat trick for string puzzles or text transformations!

Recursion on Lists

Recursion works well with lists too. Here’s how you can sum all the elements of a list by adding the first item to the sum of the rest.

def list_sum(lst):

    if not lst:  # Base case: empty list returns 0
        return 0

    return lst[0] + list_sum(lst[1:])  # Recursive step: first element + sum of the rest


print(list_sum([1, 2, 3, 4]))  # Output: 10

This example shows recursion breaking down a list one element at a time, making the problem simpler with each call until the list is empty.

Recursing Through Nested Structures

Recursion is perfect for nested structures like lists within lists. This example counts all the numbers inside a nested list, no matter how deep they go.

def count_all(data):

    total = 0

    for item in data:

        if isinstance(item, list):  # If item is a list, call count_all recursively
            total += count_all(item)
        else:
            total += 1  # Count the item if it's not a list

    return total


print(count_all([1, [2, 3], [4, [5, 6]]]))  # Output: 6

This function checks each item; if it’s a list, it dives deeper with recursion. If not, it adds to the count. It’s a neat way to explore all levels of nested data!

Check if a Word is a Palindrome

This recursive function checks if a word reads the same forwards and backwards—a palindrome. It compares the first and last letters, then keeps checking the middle part.

def is_palindrome(word):

    if len(word) <= 1:       # Base case: empty or single-letter word is a palindrome
        return True
    if word[0] != word[-1]:  # If first and last letters differ, not a palindrome
        return False

    return is_palindrome(word[1:-1])  # Check the middle part recursively


print(is_palindrome("racecar"))  # True
print(is_palindrome("banana"))   # False

This keeps peeling off the outer letters until the word is too small to check, confirming if it’s a palindrome or not. Simple and clever!

Count Digits in a Number

This recursive function counts how many digits a number has by dividing it by 10 until it’s less than 10.

def count_digits(n):

    if n < 10:          # Base case: single-digit number
        return 1

    return 1 + count_digits(n // 10)  # Count one digit and recurse with the rest


print(count_digits(12345))  # 5

Each step removes the last digit by integer division, adding 1 to the count until only one digit remains. Simple and neat!

Find Maximum in a List

This recursive function finds the largest number in a list by comparing the first element with the maximum of the rest.

def find_max(lst):

    if len(lst) == 1:       # Base case: only one element
        return lst[0]

    rest_max = find_max(lst[1:])  # Max of the remaining list

    return lst[0] if lst[0] > rest_max else rest_max  # Compare and return larger


print(find_max([5, 9, 2, 1, 8, 3]))  # 9

It breaks down the list until a single element remains, then builds back up by comparing values.

Print a Pyramid Pattern

Here’s a fun way to print a simple star pyramid using recursion.

def pyramid(n):

    if n == 0:      # Base case: stop when n reaches 0
        return

    pyramid(n - 1)  # Print smaller pyramid first (top)

    print("*" * n)  # Print stars for current level


pyramid(5)

The function first prints the smaller levels, then the current level, creating a pyramid from top to bottom.

Binary Representation of a Number

This function converts a decimal number to its binary form as a string using recursion.

def to_binary(n):

    if n == 0:      # Base case: no more bits to process
        return ""

    return to_binary(n // 2) + str(n % 2)  # Process higher bits first, then current bit


print(to_binary(10))  # 1010

The number is divided by 2 repeatedly to get bits, building the binary string from left to right.

Flatten a Nested List

This recursive function takes a nested list with any depth and returns a flat list of all the elements.

def flatten(data):

    flat = []

    for item in data:

        if isinstance(item, list):   # If item is a list, flatten it recursively
            flat += flatten(item)
        else:
            flat.append(item)         # Otherwise, add the item directly

    return flat

print(flatten([1, [2, [3, 4], 5], 6]))  # [1, 2, 3, 4, 5, 6]

It keeps digging deeper whenever it finds a list, combining all elements into a single flat list.

Capitalize Words in a List

This example uses recursion to convert all words in a list to uppercase.

def capitalize_words(words):

    if not words:            # Base case: empty list returns empty list
        return []

    return [words[0].upper()] + capitalize_words(words[1:])  # Capitalize first word, then recurse on the rest


print(capitalize_words(['dog', 'cat', 'fox']))  # ['DOG', 'CAT', 'FOX']

The function takes the first word, converts it, and then applies the same to the rest until the list is empty.

Repeat a Word N Times

This recursive function repeats a given word a specified number of times by calling itself until the count reaches zero.

def repeat(word, times):

    if times == 0:       # Base case: no more repetitions
        return ""

    return word + repeat(word, times - 1)  # Add word and recurse


print(repeat("ho", 3))  # hohoho

It builds the repeated string step by step, adding one copy of the word each call.

Sum of Digits

This function recursively adds all digits in a number.

def digit_sum(n):

    if n == 0:           # Base case: no digits left
        return 0

    return n % 10 + digit_sum(n // 10)  # Add last digit, then recurse on rest


print(digit_sum(1234))  # 10

Each call separates the last digit, sums it, and continues with the remaining digits.

Build a Nested Countdown List

This function creates a nested list counting down from a number to zero, each level containing the current number and the next nested list.

def nested_countdown(n):

    if n == 1:
        return [1]  # stop at 1

    return [n, nested_countdown(n - 1)]


print(nested_countdown(5))  # Output: [5, [4, [3, [2, [1]]]]]

Each recursive call builds a deeper nested list until zero is reached.

Count Vowels in a Word

This recursive function counts vowels in a given word by checking the first letter and then recursing on the rest.

def count_vowels(word):

    if not word:          # Base case: empty string
        return 0

    first = 1 if word[0].lower() in 'aeiou' else 0  # Check first letter

    return first + count_vowels(word[1:])            # Add and recurse


print(count_vowels("Recursion"))  # 4

It sums up 1 for every vowel found and 0 for consonants, processing the word letter by letter.

Conclusion

Recursion is a powerful way to solve problems by breaking them down into smaller, similar tasks. The key is always to have a base case that stops the function from calling itself forever. Practicing simple examples helps you get comfortable with thinking about problems in this way. It’s exciting and fun to watch how a function can call itself repeatedly to work through a problem step by step, creating elegant solutions with just a few lines of code.