Concepts / Writing Functions That Return Values

Writing Functions That Return Values

A function that returns a value hands the result back to its caller, and the caller must assign or use that result for it to matter.

  • Programming
Interactive lab

Try it: Defining and Calling Functions

What def and a call really do: def only creates a function object, a call binds the arguments to the parameters in a new frame and runs the body, return ends the call at once and its value replaces the call expression in the caller — while a function that only prints gives back None.

How it works

  1. def name(params): creates a function object and binds the name to it; the body does not run yet.
  2. A call evaluates its arguments, opens a new frame, binds each parameter to its argument and runs the body from the top.
  3. return value ends the call immediately (lines after it never run) and sends the value back; the frame and its local names are discarded.
  4. In the caller, the call expression is replaced by the returned value, which can be stored, used in a larger expression or passed to another call.
  5. A body that ends without return (e.g. one that only prints) makes the call evaluate to None — so x = f() stores None and None + 1 raises TypeError.
  6. Calling a name before its def has run raises NameError: the function does not exist yet.

Default run (18 steps): The program is ready. Python runs it from the top, one statement at a time. Idea: the value a call returns is what the caller stores. … The program finished. Console: 8.

Simplified: Four fixed, hand-written programs; you edit the literal arguments, switch the body between return and print, and move the def after the calls. The lab's own small evaluator runs them (your input is never executed as code); every variant was run in CPython 3.12 and the lab reproduces its sys.settrace line/call/return/exception events, its output and its error message. Functions are shown as <function name> without a memory address.

Educational simulation

Loading the simulation…

A Common Belief About Function Calls

When you first meet a function that returns a value, it is tempting to believe that simply calling the function is enough to make something change. You write a line that calls a helper, the helper does some work, and you expect the variable you care about to be updated automatically. This belief sounds reasonable, and it is often wrong. The mistake usually stays hidden until a program misbehaves, for example by repeating the same output forever.

This article uses a small program that generates a 3N+1 sequence as its running example. The rules are simple. The sequence begins with a starting value. If the current term N is even, the next term is N divided by 2. If N is odd, the next term is 3N+1. The sequence ends when N reaches 1. The source excerpt shows how the helper function nextN is called, but it does not show the body of nextN. So we will focus on what the calling code does with the value that nextN gives back, and we will not guess at what happens inside the helper.

Why a Call Alone Does Not Update N

The misconception goes like this: when the loop executes the call nextN(N), the variable N is somehow moved forward to the next term. The learner imagines the function reaching out and changing N directly. Under this belief, the loop body would only need to call the function, and the next pass through the loop would see the new term.

The source code does not work that way. The line inside the loop is an assignment: N is set equal to the result of nextN(N). The call produces a value, and the assignment is what stores that value in N. The belief leaves out the assignment, and that omission is where the trouble starts.

Where the Returned Value Goes

A function that returns a value hands the result back to its caller. The caller must assign or use that result for it to matter. In the 3N+1 loop, the statement N = nextN(N) both computes the next term and stores it in N. Because the assignment happens before the loop condition is checked again, the loop condition is tested against the new term rather than the old one.

python
Notes on the code
  • N = start sets the loop variable to the starting term
  • count = 1 includes the starting term in the count
  • N = nextN(N) calls the helper and stores the returned value back in N
  • The while condition N > 1 is checked again using the stored value

The sketch above is an illustrative reconstruction of the loop's structure. The source excerpt shows the call and the assignment, but it does not show the body of nextN, and this sketch does not claim anything about how that helper is written. What matters here is the flow of the value: the helper returns a result, the assignment stores it, and the next iteration reads the stored value.

N (current term)6nextN(N) callcomputes the next termReturned value3Assignment N = ...stores 3 in NLoop test N > 1checked with the new Npassed inreturnsassignednext iteration reads N
When nextN returns a value, where does that value go, and how does the next loop iteration see the new N?

A void function behaves differently. The source gives print3NSequence as an example of a function that does not return a value; it produces its output directly. Its job is to print, not to hand a number back to the caller. So a function that returns a value and a function that prints are used in different ways, and the caller of a value-returning function must do something with the result.

Tracing the Sequence Starting at 6

Before you look at the trace, make a prediction. The program starts the sequence at 6 and prints the starting term first. It then repeats the loop while N is greater than 1, computing and printing each following term. The count variable starts at 1 to include the starting term. Use the rules from earlier to work out the sequence by hand, and then decide how many terms the program reports.

What do you think happens?

Starting at 6, how many terms does the program report?

Reveal answer
Answer:

9 terms: 6, 3, 10, 5, 16, 8, 4, 2, 1

6 is even, so the next term is 3. 3 is odd, so the next term is 10. Continuing with the even and odd rules gives 5, 16, 8, 4, 2, and then 1, where the loop stops. Counting the starting term plus the eight computed terms gives 9.

Worked example

Tracing N and count through the loop

Trace the variable N and the count variable for the 3N+1 sequence starting at 6.

  1. 1
    Start

    N is 6 and count is 1. The starting term 6 is printed first.

  2. 2
    Pass 1

    N is 6, which is greater than 1. nextN(6) returns 3 because 6 is even. N = 3, count = 2, and 3 is printed.

  3. 3
    Pass 2

    N is 3, which is odd, so nextN(3) returns 10. N = 10, count = 3, and 10 is printed.

  4. 4
    Pass 3

    N = 5, count = 4. Then N = 16, count = 5. Each new value replaces the old one in N.

  5. 5
    Pass 4

    N = 8, count = 6, then N = 4, count = 7, then N = 2, count = 8.

  6. 6
    Stop

    N = 1, count = 9. The loop test N > 1 is now false, so the loop ends and the count of 9 is reported.

Result

The program prints 9 terms: 6, 3, 10, 5, 16, 8, 4, 2, 1.

N = 6count 1N = 3count 2N = 10count 3N = 5count 4N = 16count 5N = 8count 6N = 4count 7N = 2count 8N = 1count 9, loop endsnextN(N)nextN(N)nextN(N)nextN(N)nextN(N)nextN(N)nextN(N)nextN(N)
What is N on each pass through the loop, and when does the loop stop printing terms?

Unassigned Return Values and Infinite Loops

Suppose the assignment were removed, leaving only a bare call nextN(N) inside the loop. The function would still compute a value, but nothing would store it. N would keep its old value on every pass. The condition N greater than 1 is checked at the start of each iteration, and N never changes, so the condition stays true forever. The program would print the same term repeatedly and never reach the stopping point.

Before
while N > 1:
    N = nextN(N)
    print(N)

The returned value is stored in N, so N moves through 6, 3, 10, and so on until it reaches 1.

After
while N > 1:
    nextN(N)
    print(N)

The returned value is discarded, so N stays at 6 and the condition N > 1 is true on every pass.

N = nextN(N)assignment presentN3Loop test N > 1tested on 3nextN(N)bare call, no assignmentN6Loop test N > 1true on every passstoresnext checkvalue discardednext check
If nextN(N) is called but its result is not assigned to N, what value does N keep on every iteration, and why does the loop never end?

Checking Your Understanding

MEDIUM

Starting at 7, trace the 3N+1 sequence by hand and list each term. Then state how many terms the program would report. Remember that 7 is odd, so the first step is 3N+1.

Hints
  • Apply the odd rule first: 3 times 7 plus 1 gives the next term.
  • Keep applying the even rule (divide by 2) or the odd rule (3N+1) until N reaches 1.
  • Count the starting term, then add one for each computed term.
EASY

A teammate changes the loop body to a bare call nextN(N) and says the function updates N internally. Explain in two or three sentences why the loop will not progress, and write the one-line change that fixes it.

Hints
  • Ask what the assignment does that the bare call does not.
  • The fix involves storing the returned value back into N.

Key Takeaways

  • A function that returns a value hands the result back to its caller, and the caller must assign or use that result for it to matter.
  • In the 3N+1 loop, N = nextN(N) computes the next term and stores it in N, so the loop condition is tested against the new term.
  • Tracing from 6 gives the terms 6, 3, 10, 5, 16, 8, 4, 2, 1, so the program reports 9 terms.
  • A call whose return value is never stored leaves N unchanged, and the condition N greater than 1 can stay true forever, causing an infinite loop.
  • A void function such as print3NSequence produces output directly and does not return a value.