Python Computer Science
Beginner
80 mins
Teacher/Student led
What you need:
Chromebook/Laptop/PC

Recursive Algorithms and Functions

In this lesson, you'll learn the essentials of recursive algorithms and functions. Explore how recursion solves problems by breaking them into smaller subproblems, understand base cases and recursive calls, and implement examples like factorial and Fibonacci in Python.
Learning Goals Learning Outcomes Teacher Notes

Live Class Feed

This is a live feed of the latest activity by your students on this lesson. It will update in real-time as they work on the lesson.
Load previous activity

    1 - Introduction

    In this lesson, you'll explore recursive algorithms and functions, a key concept in advanced algorithms. Recursion allows functions to solve problems by breaking them into smaller, similar subproblems.

    By the end of this lesson, you will be able to:

    • Understand the basics of recursion, including base cases and recursive calls.
    • Implement recursive functions for factorial and Fibonacci in Python using VS Code.
    • Compare recursive and iterative approaches in terms of efficiency and code structure.
    • Discuss recursion limits, stack overflow, and how to debug them.
    • Apply recursion to a small project problem.
    You'll need VS Code (or a similar IDE) with the Python extension installed.

    2 - Understanding Recursion Basics

    Recursion might seem complex at first, but it’s a clever way for a function to tackle a problem by calling itself with a simpler version of that problem. Picture yourself organizing a stack of nested boxes: you open one box, then deal with a smaller set of boxes inside it, repeating the process until you find an empty box. That’s recursion.

    Every recursive function has two main parts:

    1. Base Case: This is the stopping point, where the problem is so simple that you don’t need to break it down further. Without this, the function would keep calling itself endlessly, like an infinite loop! For example, when counting all files in a folder (including subfolders), the base case is an empty folder, which has 0 files.
    2. Recursive Case: This is where the real work happens. The function calls itself with a smaller or simpler version of the problem, moving closer to the base case each time. For counting files, you count the files directly in the current folder and then add the number of files in each subfolder by applying the same process to those subfolders, shrinking the problem until you reach empty folders.

    When a function calls itself, it uses something called the 'call stack' – think of it as a stack of plates. Each call adds a plate to the stack. When it reaches the base case, it starts removing plates (returning values) from the bottom up, building the final answer.

    Let's look at a simple example: Suppose you want to count down from 5 to 0. A recursive function would print 5, then call itself to count down from 4, then from 3, and so on. When it reaches 0 (the base case), it stops and doesn't call itself anymore. So the output is 5, 4, 3, 2, 1, 0.
    Recursion is really useful for problems that naturally break into smaller similar problems, like exploring family trees or sorting lists in clever ways. However, it can sometimes be slower or use more computer memory than a regular loop (called iteration) because of that stack of calls.

    3 - Difference Between Iterative and Recursive

    Let's take a look at the key differences between iterative and recursive approaches, building on the examples you've seen in this lesson, like factorial and Fibonacci.

    Recursive Approach: A recursive function solves a problem by calling itself with a smaller version of the same problem. It relies on a base case to stop the recursion and recursive cases to break down the problem. For example, in the recursive factorial, each call computes n * factorial(n-1) until reaching the base case (n=0 or 1). This uses the call stack, which can lead to stack overflow for large inputs, and may be less efficient due to repeated calculations.


    Iterative Approach: An iterative function uses loops (like for or while) to repeat steps without calling itself. It builds the solution step by step, often using variables to track progress. For instance, the iterative factorial multiplies numbers in a loop from 1 to n, using constant space (O(1)) and linear time (O(n)). It's generally more efficient, avoids recursion depth issues, and is better for large inputs.

    Key Differences:

    • Efficiency: Recursion can be slower and use more memory (O(n) space for stack); iteration is often faster with less memory.
    • Code Structure: Recursion is more concise and natural for problems like tree traversals; iteration can be longer but clearer for simple tasks.
    • Risks: Recursion risks stack overflow; iteration risks infinite loops if not handled properly.
    Choose recursion for clarity in divide-and-conquer problems, but prefer iteration for performance in straightforward cases.

    4 - Implementing Recursive Factorial

    In VS Code, create a new folder called RecursionLesson and a file named recursion.py inside it.

    Calculating the factorial of a number involves multiplying the number by the factorial of the number one less than it. For example, the factorial of 5 (written 5!) is 5 * 4 * 3 * 2 * 1 = 120. Recursion works here because each factorial can be broken down into a smaller factorial problem.

    We'll write a recursive function to compute the factorial of a number, where the base case is 0! or 1! (both equal 1), and the recursive case multiplies the number by the factorial of the number minus one.

    Add the following complete code to your file:

    def factorial(n):
        # Base case: if n is 0 or 1, the factorial is 1
        if n == 0 or n == 1:
            return 1
        
        # Recursive case: calculate factorial by multiplying n with factorial of (n-1)
        previous_factorial = factorial(n - 1)
        current_factorial = n * previous_factorial
        
        return current_factorial
    
    # Test the function
    print("Factorial of 5:", factorial(5))  # Output: 120
    print("Factorial of 3:", factorial(3))  # Output: 6
    print("Factorial of 0:", factorial(0))  # Output: 1

    Save and run the file in VS Code terminal with python factorial.py. You should see 120, 6, and 1 printed.

    Try testing with other inputs like print(factorial(4)) (should print 24) or print(factorial(1)) (should print 1) to verify.

    This works by breaking down each factorial: for 5!, it computes 5 * 4!, where 4! = 4 * 3!, and so on, until it reaches the base case (1! or 0! = 1). The results are multiplied back up to get 120 for 5!.

    5 - Iterative Factorial and Comparison

    Now, let's implement an iterative version of factorial using a loop, and compare it to the recursive one. Iteration means using a loop to repeat steps, which can often be more efficient than recursion for certain problems.

    In the iterative approach, we'll start with a result of 1 and then multiply it by each integer from 1 up to n in a loop. This builds up the factorial step by step without calling the function repeatedly.

    For example, for n=5, it starts with result=1, then multiplies by 1 (still 1), by 2 (becomes 2), by 3 (becomes 6), by 4 (becomes 24), by 5 (becomes 120).

    Add the following complete code below the recursive factorial in 'recursion.py':

    def iterative_factorial(n):
    # Base cases: if n is 0 or 1, return n directly if n == 0: return 0 if n == 1: return 1 # Recursive case: calculate Fibonacci by adding the two previous numbers # Call the function for (n-1) and (n-2) and add their results previous_one = iterative_factorial(n - 1)
    previous_two = iterative_factorial(n - 2)
    current_fibonacci = previous_one + previous_two return current_fibonacci # Test the function print("Recursive:", factorial(5)) # 120 print("Iterative:", iterative_factorial(5)) # 120
    Run the file again. Both should output 120.

    Comparison:

    • Recursive: Simpler code, but uses more stack space (O(n) space complexity) and can cause stack overflow for large n, because each call adds to the call stack.
    • Iterative: Uses constant space (O(1)), more efficient for large n since it doesn't build up a stack, but the code is slightly longer. It's like doing the multiplications in a straight line rather than nesting them.
    • Both have O(n) time complexity, meaning they take time proportional to n.

    Recursion is great for clarity and problems that naturally fit a 'divide and conquer' approach, but iteration often wins for performance in simple cases like this, especially to avoid running out of memory with deep recursion.

    Unlock the Full Learning Experience

    Get ready to embark on an incredible learning journey! Get access to this lesson and hundreds more in our Digital Skills Curriculum.

    Copyright Notice
    This lesson is copyright of DigitalSkills.org. Unauthorised use, copying or distribution is not allowed.
    πŸͺ Our website uses cookies to make your browsing experience better. By using our website you agree to our use of cookies. Learn more