Home
Tail Recursion Elimination in Python

Tail Recursion Elimination in Python

4 min read · January 08, 2025

Tail recursion is a special case of recursion where the recursive call is the last thing executed by the function. Some programming languages like Scheme and Scala optimize tail recursion by reusing the same stack frame for each recursive call. This optimization is called tail call elimination.

Long story short, Python does not optimize tail recursion. And probably never will, since Guido van Rossum, the creator of Python, has this and this argument against it.

The other day my colleagues and I were discussing about functional programming and recursion. One of them mentioned that Python does not optimize tail recursion. I thought it would be fun to try and implement a tail recursion elimination decorator in Python. So here it is (I later found this blog post by Kay Schlühr with some nice ideas, so I tweaked my code with some of them - Kay’s version is perfect for Python’s features at the time, I just added a few new things):

from functools import update_wrapper

class tail_recursion:
    def __init__(self, func):
        self.func = func
        update_wrapper(self, func)

        self.recurse_sentinel = object()
        self.kwargs = None

    def __call__(self, *args, **kwargs):
        if self.kwargs is None:
            self.args, self.kwargs = args, kwargs
            try:
                while (ret := self.func(*self.args, **self.kwargs)) is self.recurse_sentinel: pass
                return ret
            finally:
                self.kwargs = None
        else:
            self.args, self.kwargs = args, kwargs
            return self.recurse_sentinel

Here’s how it can be used:

@tail_recursion
def factorial(n, acc=1):
    '''Calculate factorial of n'''
    if n == 0:
        return acc
    else:
        return factorial(n - 1, n * acc)

print(factorial(1000))

In case you are curious, the result is:

402387260077093773543702433923003985719374864210714632543799910429938512398629020592044208486969404800479988610197196058631666872994808558901323829669944590997424504087073759918823627727188732519779505950995276120874975462497043601418278094646496291056393887437886487337119181045825783647849977012476632889835955735432513185323958463075557409114262417474349347553428646576611667797396668820291207379143853719588249808126867838374559731746136085379534524221586593201928090878297308431392844403281231558611036976801357304216168747609675871348312025478589320767169132448426236131412508780208000261683151027341827977704784635868170164365024153691398281264810213092761244896359928705114964975419909342221566832572080821333186116811553615836546984046708975602900950537616475847728421889679646244945160765353408198901385442487984959953319101723355556602139450399736280750137837615307127761926849034352625200015888535147331611702103968175921510907788019393178114194545257223865541461062892187960223838971476088506276862967146674697562911234082439208160153780889893964518263243671616762179168909779911903754031274622289988005195444414282012187361745992642956581746628302955570299024324153181617210465832036786906117260158783520751516284225540265170483304226143974286933061690897968482590125458327168226458066526769958652682272807075781391858178889652208164348344825993266043367660176999612831860788386150279465955131156552036093988180612138558600301435694527224206344631797460594682573103790084024432438465657245014402821885252470935190620929023136493273497565513958720559654228749774011413346962715422845862377387538230483865688976461927383814900140767310446640259899490222221765904339901886018566526485061799702356193897017860040811889729918311021171229845901641921068884387121855646124960798722908519296819372388642614839657382291123125024186649353143970137428531926649875337218940694281434118520158014123344828015051399694290153483077644569099073152433278288269864602789864321139083506217095002597389863554277196742822248757586765752344220207573630569498825087968928162753848863396909959826280956121450994871701244516461260379029309120889086942028510640182154399457156805941872748998094254742173582401063677404595741785160829230135358081840096996372524230560855903700624271243416909004153690105933983835777939410970027753472000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Explanation

In Python we can create decorators with functions or classes. In this case, it would be useful to store some things in the decorator itself, so I decided to go with a class. We start by registering the decorator instance as a wrapper over func. If you want to know more about it, read the docs on update_wrapper.

The key to eliminating the recursion stack is the recurse_sentinel. We use this object’s identity as a sentinel to tell our loop that the function has been called recursively. We also store the arguments passed to the current function call. Since this is a tail recursion, we expect the arguments to keep changing until we reach a base case.

The original function call

When the function is first called, the value of self.kwargs is None. In every subsequent recursive call, even if no keyword argument is passed, the value will be a dict. The if block will be executed. We initialize the attributes that store the arguments with the original arguments and then we start calling the function in a loop.

The loop

I used the walrus operator to store the returned value in ret. It is only used for the final answer. In all previous calls, ret will be recurse_sentinel.

So, what happens here? When we call self.func, its code will be executed. But when it calls itself recursively, our decorator will be called first. Since this is not the original call, we will always end up in the else block. It will simply store the arguments of this new recursive call and return the sentinel.

So we are back at the loop, but now self.args and self.kwargs are updated. Now we can perform the actual function call again. We don’t need to do anything inside the loop, just keep calling the function iteratively until we get to a base case.

The finally

The last thing we are missing is resetting self.kwargs to None. If we don’t do this, the next time the function is called, it will not work. That’s why we need to make sure it will be reset, even if an error occurs. Which is also related to one of the reasons Guido van Rossum thinks adding tail recursion elimination is a bad idea : the stack trace gets messy.

And that’s it for my decorator. It is not really useful, but it was fun afternoon project. Let me know if you have ideas to improve this decorator!