Maximum Recursion Depth in Python  

Rate this post

What is Recursion?

Recursion in programming is a problem-solving concept.

In recursion, a function finds the solution by calling itself once or many times. This function call can be explicit or implicit.

💡Info: Recursion, according to (Tang 2013), is when a function or algorithm calls itself one or more times. These calls occur until the program meets a specified condition. When met, processing of repeated calls from the last one called to the first happens.

See below an example of a recursive factorial function.

def factorial(n):
    """
    Calculate n!

    Args:
        n(int): factorial to be computed
    Returns:
        n!
    """
    if n == 0:
        return 1
    return n * factorial(n-1)

print(factorial(3))
# 6

In the highlighted line in the above snippet the factorial function calls itself. This function calls itself again and again.

This continues until the condition on line 10 is fulfilled.

Then, the previous function calls are evaluated up to the initial call. The condition n == 0 is a base case.

💡 Info: A base case is very important in a recursive function since it defines the end of the recursive calls. If there exists a faulty base case or a non-existent one in a recursive function, the function calls would go on indefinitely, akin to an infinite while loop.

Recursion utilizes stacks in function calls. Hence, indefinite function calls lead to a C (programming language) stack overflow. This stack overflow, in turn, crashes Python. A size limit introduced to the python interpreter stack prevents potential stack overflow. 

See also: sys — System-Specific Parameters and Functions and below for the call stack in the global frame when the last line evaluates.

You can try it yourself in the memory visualizer:

Or you just have a look at the screenshots taken from my execution flow:

A stack frame from a recursive call is a data structure. It contains the variable of a function call parameters at the specific function call. It holds the state of the recursive function at an instance, with specific arguments.

As highlighted below, the return value of each successive call changes according to the argument passed into the recursive call.

When the argument is 0 the return value is 1. When the argument is 1 the return value is 1, and so on until the initial argument of 3, which has a return value of 6.

Types of Recursions

There are mainly two types of recursion. These types are direct and indirect recursion.

For direct recursion, the recursive call is explicitly declared (see code snippet below). 

def direct_recursion(n):
    if n == 0:
        return 0
    return direct_recursion(n-1)
direct_recursion(4)

Yet, in indirect recursion, the recursive function calls another function which in turn calls it.

For example, we define a new function named indirect_recursion(n). indirect_recursion(n) calls a function called other_function(3). Inside other_function(n) we call indirect_recursion(n) again.

This is a case of indirect recursion.

def indirect_recursion(n):
    if n == 0:
        return 0
    return n - other_function(n-1)

def other_function(n):
    if n > 0:
        n -= 2
    return indirect_recursion(n)

indirect_recursion(3)

Besides the above, there are other types of recursion.

There is also tail recursion and head recursion.

  • Head recursion, refers to when the recursive call is at the beginning of a function.
  • Tail as the name suggests refers to the scenario where the recursive call is the last line of the function.

In the direct recursion snippet above, the last line in the function is a sole recursive call.

This is an example of a tail-recursive function. Hence, tail recursion is a particular example of a direct recursion type.

Note, in our recursive factorial function, the last line contains the recursive call. But, it does not qualify to be tail-recursive. This is because the very last operation in that function is multiplication. 

Tail call optimization

A tail call is not unique to recursive functions.

It refers to the last action that is finally performed by a function or a procedure.

As explained above, if the final action is recursive then the tail call can is a tail-recursion.

Some programming languages like scheme put in place tail call optimization. Tail call optimization ensures constant stack space usage. In (“Tail Call” 2022), tail call optimization, the call stack receives no more stack frames.

Since most of the current function state is no longer needed, hence replaced by the stack frame of the tail call.

As highlighted in the image illustration of a stack frame in the context of a recursive function. Instead of each call generating a new stack frame. This is achieved by modifying the current frame to align with the current argument. This is a powerful technique that allows for the conservation of memory.

Hence, preventing stack overflow in cases of tail recursion functions. As highlighted in this answer (Cronin 2008). The amount of space required for a recursive factorial function is constant for any value argument.

Tail Call Optimization in Python

By design, python, unlike languages like scheme, does not support tail call optimization.

This is true for all tail calls, including tail-recursive calls. The main reason for this is python’s emphasis on having complete debug information. This debug information relies on stack traces.

We lose debug info in discarded stacks by implementing tail call optimization. This renders stack trace useless.

Currently, Python, by default, allows for 1000 recursion calls.  After exceeding these calls, Python raises a RecursionError: maximum recursion depth exceeded.

How to Get the Current Recursion Limit in Your System in Python?

The code listing below shows how to find out the current recursion limit in your system.

import sys
print(sys.getrecursionlimit())

The default is usually 1000 but it depends on the set-up one is running.

In my current set-up using Anaconda, the recursion limit is 3000.

Recursion limit refers to the number of function calls python allows when recursing.

How to Set the Recursion Limit in Python?

It is possible to change the recursion limit. By adding the following code we get rid of RecursionError if the solution lies within the set limit.

sys.setrecursionlimit(3500)

It is important to note that increasing the recursion limit does not change the C-stack size.

Hence, even with increasing the limit stack overflow might still occur since the limit is a safety measure to prevent stack overflow.

The better option might be refactoring the solution. For example, using an iterative solution using loops, and other built-in Python sequences.

References

  • Cronin, Kyle. 2008. “Answer to ‘What Is Tail Call Optimization?’” Stack Overflow. https://stackoverflow.com/a/310980.
  • “Sys — System-Specific Parameters and Functions — Python 3.10.4 Documentation.” n.d. Accessed April 26, 2022. https://docs.python.org/3/library/sys.html#sys.setrecursionlimit.
  • “Tail Call.” 2022. In Wikipedia. https://en.wikipedia.org/w/index.php?title=Tail_call&oldid=1082917459.
  • Tang, Daisy. 2013. “CS240: Data Structures & Algorithms I.” March 2013. https://www.cpp.edu/~ftang/courses/CS240/lectures/recursion.htm.