The stack is unnecessary when you're dealing with tail-call recursion. It's possible to automatically rewrite it in terms of a while loop like so:

def fact3(n, curr_value=1):
    while n != 0:
        curr_value = curr_value * n
        n = n - 1

    return curr_value

# the recursive version, for reference
def fact2(n, curr_value=1):
    if n == 0:
        return curr_value
    else:
        return fact2(n - 1, curr_value * n)

The values that n and curr_value take in fact3 will exactly match the sequence of function calls in evaluating fact2.

The stack is unnecessary when you're dealing with tail-call recursion. It's possible to automatically rewrite it in terms of a while loop like so:

def fact3(n, curr_value=1):
    while n != 0:
        curr_value = curr_value * n
        n = n - 1

    return curr_value

# the recursive version, for reference
def fact2(n, curr_value=1):
    if n == 0:
        return curr_value
    else:
        return fact2(n - 1, curr_value * n)

The values that n and curr_value take in fact3 will exactly match the sequence of function calls in evaluating fact2.