Skip to main content

Recursion in Python: Concepts, Examples, and Tips

Learn recursion in Python with examples, key concepts, and practical tips. Understand base cases, recursive functions, and when to use recursion over iteration.
Apr 9, 2025  · 9 min read

Recursion is a fundamental concept in programming, and it has particular importance in Python. It refers to the technique where a function calls itself to solve a problem, breaking it down into smaller, manageable subproblems. 

This method allows for elegant and concise solutions, especially when dealing with problems that involve repetitive tasks or hierarchical structures. In Python, recursion is used across a variety of applications, from sorting algorithms to complex data traversal. 

Recursive solutions often mirror the mathematical definitions of problems, making them intuitive for those familiar with mathematical reasoning. The beauty of recursion lies in its ability to express complex algorithms in just a few lines of code, creating solutions that are both elegant and readable. However, mastering recursion requires a shift in thinking from the more common iterative approaches.

In this article, I will explore the concept of recursion, how it works in Python, and its practical applications, including a discussion of common issues and comparisons to iteration. If you are new to Python, consider taking one of our courses, such as Introduction to Python, Programming Paradigm Concepts, or Python Programming Fundamentals.

What Is Recursion in Python?

In Python, recursion refers to a function calling itself to solve a problem. It involves two critical components:

  • Base case: This is the condition that terminates the recursion. Without it, the recursive calls would continue forever, eventually causing the function to crash or exhaust available memory.
  • Recursive case: This part of the function involves breaking down the problem into smaller parts and calling the function again with those smaller parts.

Here is a simple way to think about recursion: imagine a function that solves a complex problem by solving smaller versions of the same problem, eventually reaching a solution that does require further recursion.

The recursive approach follows the "divide and conquer" strategy, breaking down complex problems into simpler versions until reaching a trivial case. This approach often mirrors the way we naturally think about solving problems. For instance, when sorting a deck of cards, we might naturally sort smaller groups and then combine them, which is essentially how recursive sorting algorithms like merge sort work.

Every recursive function must have at least one base case and at least one recursive case. The base case prevents infinite recursion by providing a condition that does not require further function calls. The recursive case reduces the problem size with each call, ensuring that the base case is eventually reached.

How Does Recursion Work in Python?

Recursion works by allowing a function to call itself with modified arguments, gradually solving the problem in smaller steps. To understand this more concretely, consider the task of calculating the sum of numbers from 1 to num.

Here is a simple recursive approach to calculate the sum:

def sum_recursive(num):
    if num == 1:  # Base case
        return num
    return num + sum_recursive(num - 1)  # Recursive case

print(sum_recursive(3)) # 3 + 2 + 1 
6

In this function, the base case is when num == 1, which stops the recursion. Otherwise, the function calls itself with num - 1 until it reaches the base case.

How it works step-by-step:

  1. sum_recursive(3) calls sum_recursive(2).
  2. sum_recursive(2) calls sum_recursive(1).
  3. sum_recursive(1) returns 1 (base case).
  4. Now, sum_recursive(2) can return 2 + 1 = 3, and sum_recursive(3) can return 3 + 3 = 6.

Another example would be a recursive function to calculate the power of a number:

def power(base, exponent):
    if exponent == 0:  # Base case
        return 1
    else:
        return base * power(base, exponent - 1)  # Recursive case

print(power(2, 3))
8

Step-by-step explanation of this power function:

  • power(2, 3) calls power(2, 2).
  • power(2, 2) calls power(2, 1).
  • power(2, 1) calls power(2, 0).
  • power(2, 0) returns 1 (base case).
  • Now, working backwards: power(2, 1) returns 2 * 1 = 2.
  • Then power(2, 2) returns 2 * 2 = 4.
  • Finally, power(2, 3) returns 2 * 4 = 8.

When a recursive function is called, each recursive call creates a new entry on the call stack. This stack keeps track of all function calls and their variables. When the base case is reached, the stack begins resolving each call in reverse order, computing the final result step by step.

Understanding this stack behavior is crucial because it explains both the power and limitations of recursion in Python. It elegantly preserves context but can also lead to memory issues with deep recursion.

Implementing Factorial in Python Using Recursion

The factorial function is a classic example used to demonstrate recursion. The factorial of a number n, denoted n!, is the product of all positive integers less than or equal to n. Mathematically:

n! = n × (n − 1) × (n − 2) × … × 1

Recursively, the factorial function can be defined as:

n! = n × (n − 1)!

Let’s implement this in Python:

def compute_factorial(num):
    if num == 0:  # Base case
        return 1
    return num * compute_factorial(num - 1)  # Recursive case

print(compute_factorial(4)) # 4 * 3 * 2 * 1
24

Step-by-step explanation of the code:

  • The function compute_factorial(num) checks if num == 0 and returns 1 in that case.
  • Otherwise, it returns num * compute_factorial(num - 1), where the function calls itself with a smaller value of num.

The factorial function perfectly illustrates the elegance of recursion. The mathematical definition itself is recursive, making the code implementation intuitive and closely aligned with the mathematical concept. This is one of the strengths of recursion, it allows code to directly express mathematical definitions.

It is worth noting that there are edge cases to consider. For instance, factorial is typically only defined for non-negative integers. A robust implementation might include error handling for negative inputs or type checking. 

Additionally, for large values of num, the recursive solution can lead to a stack overflow, which is a limitation I will discuss in more detail.

Common Issues: How to Fix Recursion Errors in Python

While recursion can be a powerful tool, it is prone to a few common issues, such as:

Exceeding maximum recursion depth

Python has a default recursion limit of 1000 calls. If a recursive function calls itself too many times, a RecursionError will be raised.

Here’s how to fix it:

You can increase the recursion depth using sys.setrecursionlimit(). However, this is not recommended unless necessary, as it may cause a stack overflow.

import sys  

# Get the current recursion limit
current_limit = sys.getrecursionlimit()
print(f'Current limit: {current_limit}')  

# Set a new recursion limit to 200
new_limit = 200
sys.setrecursionlimit(new_limit)  

# Get the new recursion limit
changed_current_limit = sys.getrecursionlimit()
print(f'Current limit after change: {changed_current_limit}')  
Current limit: 1000
Current limit after change: 200

Missing base case

A missing or incorrect base case will cause the function to recurse infinitely, leading to a stack overflow.

To fix it, ensure that your base case is well-defined and reachable.

How to Stop Recursion in Python

The key to stopping recursion in Python is defining a proper base case. Without it, the recursion will never end and result in an error.

For example, in the factorial function I implemented earlier, the base case was if num == 0, which stops the recursion. In general, you need to ensure that every recursive function has a condition under which it terminates.

Designing effective base cases requires careful thought. Some guidelines include:

  • Identify the simplest version of the problem that can be solved directly.
  • Ensure that all recursive paths eventually reach a base case.
  • Test edge cases separately to verify they are handled correctly.
  • Consider multiple base cases if the problem requires it.

Sometimes, defensive programming techniques are helpful, adding guards against invalid inputs or runtime checks to prevent excessive recursion depth. For example:

def safe_factorial(num, depth=0, max_depth=100):
    # Check if recursion is too deep
    if depth > max_depth:
        raise ValueError("Maximum recursion depth exceeded")
   
    # Base case
    if num <= 0:
        return 1
   
    # Recursive case with depth tracking
    return num * safe_factorial(num - 1, depth + 1, max_depth)
# Calculate factorial of 5 with default depth limits
result = safe_factorial(5)

print(result) 
120
# Calculate factorial of 5 with depth > max_depth
result = safe_factorial(5, depth=20, max_depth=10)

print(result) 
ValueError: Maximum recursion depth exceeded

This approach allows you to set custom limits and provide more meaningful error messages than the default RecursionError.

Here is another example that demonstrates a different way to define base cases: finding the greatest common divisor (GCD) of two numbers using recursion.

The GCD of two numbers x and y can be computed using Euclid's algorithm, with the modulo operator %:

  • Base Case: If y == 0, the GCD is x.
  • Recursive Case: Otherwise, the GCD of x and y is the same as the GCD of y and x % y.
def find_gcd(x, y):
    # Base case: if y is 0, the GCD is x
    if y == 0:
        return x
    # Recursive case: GCD of y and x % y
    return find_gcd(y, x % y)

print(find_gcd(48, 18))  
6

Recursion vs. Iteration in Python

Both recursion and iteration can solve many of the same problems, but each has its pros and cons:

  • Recursion is often more elegant and easier to understand, especially for problems that have a natural recursive structure (e.g., tree traversals, factorials, Fibonacci numbers).
  • Iteration can be more efficient because it does not involve the overhead of multiple function calls and can avoid issues related to stack overflow. Iterative solutions are usually more memory-efficient.

When to use recursion:

  • When the problem can be naturally divided into smaller subproblems.
  • For problems involving hierarchical data structures, like trees.
  • When the solution in recursive form is more intuitive and leads to cleaner code.
  • When code readability is prioritized over performance optimization.
  • When working with data of unknown depth (like parsing nested JSON or XML).

When to use iteration:

  • For problems that require repetitive tasks without dividing the problem into smaller subproblems.
  • For performance-critical tasks, where deep recursion could result in high memory usage.
  • When working with large datasets that might exceed the recursion limit.
  • When memory usage is a primary concern.
  • For problems that are naturally sequential rather than hierarchical.

Let's compare recursive and iterative implementations of the same problem, calculating the sum of numbers from 1 to num:

Recursive:

def sum_recursive(num):
    if num == 1:
        return 1
    else:
        return num + sum_recursive(num - 1)

Iterative:

def iterative_sum(num):
    total = 0
    for i in range(1, num + 1):
        total += i
    return total

The iterative version uses a loop to accumulate the sum, while the recursive version breaks the problem down into adding n to the sum of numbers from 1 to num - 1. While both solutions work, the iterative version will be more efficient for large values of num, as it does not involve the overhead of multiple function calls.

Some problems that are naturally recursive can be transformed into iterative solutions using a stack or queue to simulate the recursive calls. This technique is particularly useful when dealing with tree or graph traversals.

Practical Examples of Recursion in Python

Recursion can be applied to a variety of interesting problems:

1. Calculating Fibonacci numbers

The Fibonacci sequence is defined recursively as:

  • F(0) = 0
  • F(1) = 1
  • F(n) = F(n − 1) + F(n − 2)

This will provide the following sequence:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89,...

Here is how you could implement it:

def compute_fibonacci(num):
    if num <= 0:
        return 0
    elif num == 1:
        return 1
    return compute_fibonacci(num - 1) + compute_fibonacci(num - 2)

print(compute_fibonacci(6))
8

However, this naive implementation recalculates values multiple times. A more efficient approach uses memoization, which stores previously computed results (caching) to avoid redundant calculations, and significantly reducing computation time:

def optimized_fibonacci(num, cache=None):
    if cache is None:
        cache = {}
    if num in cache:
        return cache[num]
    if num <= 1:
        return num
    cache[num] = optimized_fibonacci(num - 1, cache) + optimized_fibonacci(num - 2, cache)
    return cache[num]

This can be further simplified using the  lru_cache() decorator from the functools module:

from functools import lru_cache

@lru_cache(maxsize=None)  # Cache all computed values
def cached_fibonacci(num):
    if num <= 1:
        return num
    return cached_fibonacci(num - 1) + cached_fibonacci(num - 2)

2. Traversing nested data structures

Recursion is also handy for working with nested data structures like lists of lists or JSON objects. For example, traversing a directory tree structure or recursively flattening a nested list.

def flatten(nested):
    result = []
    for element in nested:
        if isinstance(element, list):
            result.extend(flatten(element))
        else:
            result.append(element)
    return result

nested_structure = [1, [2, 3], [4, [5, 6]]]

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

3. Sorting algorithms (e.g., quick sort or merge sort) 

Sorting algorithms like quick sort and merge sort rely heavily on recursion. In these algorithms, the problem is broken down into smaller subproblems, which are sorted recursively.

Here is a simple implementation of quicksort:

def quicksort_algo(array):
    if len(array) <= 1:
        return array
    pivot = array[len(array) // 2]
    lower = [item for item in array if item < pivot]
    equal = [item for item in array if item == pivot]
    greater = [item for item in array if item > pivot]
    return quicksort_algo(lower) + equal + quicksort_algo(greater)

array = [220, 33, 400, 150, 16]

print(quicksort_algo(array))
[16, 33, 150, 220, 400]

Conclusion

Recursion is a powerful concept in Python that helps solve complex problems by breaking them down into simpler subproblems. When used correctly, recursion leads to clean, concise code that is easy to understand. 

However, it is essential to define proper base cases and be mindful of recursion depth to avoid common errors. 

While recursion can sometimes be less efficient than iteration, it is an invaluable tool for solving certain types of problems, particularly those involving hierarchical data or problems that naturally lend themselves to recursive solutions.

By understanding recursion and practicing with examples like factorials, Fibonacci sequences, and sorting algorithms, you will be well-equipped to leverage recursion in your Python projects.

If you are interested in more advanced Python topics where you could implement recursion, check out the following learning paths: Python Programming, Data Analyst in Python, and Associate Data Scientist in Python

Recursion in Python FAQs

How do base cases work in recursion?

Base cases are conditions that stop the recursion. They prevent the function from calling itself indefinitely and provide a direct solution for the simplest form of the problem.

Can recursion be more efficient than iteration?

While recursion is often more elegant and intuitive for problems with a natural recursive structure, iteration can be more memory-efficient and faster for simple repetitive tasks.

What are common errors in recursion?

Common errors include exceeding the maximum recursion depth, missing a base case, or having an incorrectly defined base case, which can lead to infinite recursion and stack overflow.

When should I use recursion in Python?

Use recursion when the problem involves breaking it into smaller sub-problems (like tree traversal or calculating factorials) or when the recursive solution is more intuitive and cleaner than iterative alternatives.

Topics

Top Python Courses

Track

Python Programming

19hrs hr
Level-up your programming skills. Learn how to optimize code, write functions and tests, and use best-practice software engineering techniques.
See DetailsRight Arrow
Start Course
See MoreRight Arrow
Related

Tutorial

Understanding Recursive Functions in Python

In this tutorial, learn about the different aspects of recursive functions and implement a recursive function in Python from scratch.
Sayak Paul's photo

Sayak Paul

12 min

Tutorial

Depth-First Search in Python: Traversing Graphs and Trees

Discover the essentials of depth-first search for navigating graphs and trees. Implement DFS in Python using recursion and iteration, and see how DFS compares to breadth-first search and Dijkstra’s algorithm.
Amberle McKee's photo

Amberle McKee

8 min

Tutorial

Python Loops Tutorial

A comprehensive introductory tutorial to Python loops. Learn and practice while and for loops, nested loops, the break and continue keywords, the range function and more!
Satyabrata Pal's photo

Satyabrata Pal

15 min

Tutorial

Fibonacci Sequence in Python: Learn and Explore Coding Techniques

Discover how the Fibonacci sequence works. Explore its mathematical properties and real-world applications.
Laiba Siddiqui's photo

Laiba Siddiqui

6 min

Tutorial

Python List Functions & Methods Tutorial and Examples

Learn about Python List functions and methods. Follow code examples for list() and other Python functions and methods now!
Abid Ali Awan's photo

Abid Ali Awan

7 min

Tutorial

Linear Search in Python: A Beginner's Guide with Examples

Explore how linear search works and why it’s ideal for small, unsorted datasets. Discover simple Python implementations, including iterative and recursive methods, and learn when to choose linear search over other algorithms.
Amberle McKee's photo

Amberle McKee

8 min

See MoreSee More