Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my if/else statement to run recursion inside a python function make the loop go twice even though while loop is flagged to stop?

I have this code which uses recursion:

def rec():
     n1=None
     run=True
     while run:
         if n1 == None:
             n1=int(input('first number'))
         n2=int(input('second number'))
         out=n1+n2

         i=input(f'{out}enter r f or e')

         if i=='e':
             run=False
         elif i=='r':
             n1=out
         elif i=='f':
             rec()
rec()

The purpose is to compute sums, possibly using the result from the previous sum for one of the inputs. After each numeric input, the user is prompted to input e to exit the program, r to use the result as an input for the next sum, or f to start fresh (ask for both inputs). My idea is to use recursion to implement the logic for f.

I test the code by selecting f for the first run and e for the second run. When I try this, I get prompted for input a third time.

Why does this happen?

like image 648
AJ Kun War Avatar asked Feb 11 '26 21:02

AJ Kun War


1 Answers

A common misunderstanding with recursion is thinking that returning from a call unwinds the entire recursive call chain and returns control to the original caller. This is untrue--you can't skip over any calls on the way back up the call stack. Returning from a recursive call only returns control (pops the call stack once) to its immediate calling function, which resumes running where it left off.

Additionally, each function call has its own set of local variables (state) that no other call can read or modify. You can share data between functions through parameters, return values, and global, nonlocal and class variables, but none of these come into play in your program (that's a good thing; it's good to keep data as local as possible).

In both respects, the behavior is no different than non-recursive functions.


With that background context, let's examine your top-level call. When you (as a user) enter f to make this block execute:

elif i=='f':
    rec()

the calling function pauses execution and runs rec() recursively. The child starts out as the parent call did, with a totally fresh set of variables. In the new child rec() call, the while loop runs and you enter a second set of numbers. Next, you enter e to break the loop by setting run to False in the child. Control reaches the end of the child call and returns back to the caller.

When the top-level rec() call resumes, the next iteration of the while loop executes because run is still True within that function. Remember, run is a purely local variable scoped to each call, so the fact that the (now non-existent) child call had run set to False doesn't matter to the parent.

As illustrated by the above example walkthrough, your current design requires that the user enter an e for every f they enter. In other words, each e only ends the current recursive call, not all of the recursive calls.

The easiest way to avoid this is to just return unconditionally after each child call:

if i=='e':
    # the user wants to exit; just return right
    # away instead of flipping a boolean flag
    return
elif i=='r':
    n1=out
elif i=='f':
    # recurse, but when we get back to this call, end
    # it immediately instead of testing the loop again
    return rec()

One fewer boolean flag makes for less mutable (modifiable) state, which means the program is easier to understand. Getting rid of run is a useful simplification even if there were no bug here.


All that said, recursion is fundamentally inappropriate for this use case. It's confusing, non-idiomatic for Python, and will eventually crash if the user tries to make more than about 1000 recursive calls.

Generally, only use recursion for divide & conquer scenarios where the call stack grows logarithmically rather than linearly. (If this makes no sense right now, don't worry--you can avoid recursion completely until you take an algorithms course, which will make this clear).

Since you already have a loop, I'd suggest using it for managing the whole operation. Something like:

def interactively_add_numbers():
    n1 = None

    while True:
        if n1 is None:
            n1 = int(input("first number: "))

        n2 = int(input("second number: "))
        result = n1 + n2

        response = input(
            f"result = {result}\n"
            "  'r' (rerun with result as n1)\n"
            "  'f' (start fresh)\n  'e' (exit)\n> "
        )

        if response == "e":
            return
        elif response == "r":
            n1 = result
        elif response == "f":
            n1 = None


interactively_add_numbers()

As an exercise, I suggest handling input errors and type errors. If the user enters a letter where a number is expected, the program ungracefully crashes. If the user enters an option other than 'e', 'r', 'f', the program runs again with n1 unchanged, which is surprising.


As an aside, always format your code with Black so it's readable to other programmers.

like image 63
ggorlen Avatar answered Feb 14 '26 10:02

ggorlen



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!