Subroutine Parameters and Return Values
A parameterized subroutine declares a parameter list in its definition, which specifies the number and types of values a caller must supply.
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
- def name(params): creates a function object and binds the name to it; the body does not run yet.
- A call evaluates its arguments, opens a new frame, binds each parameter to its argument and runs the body from the top.
- return value ends the call immediately (lines after it never run) and sends the value back; the frame and its local names are discarded.
- 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.
- 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.
- 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.
Loading the simulation…
A Subroutine That Needs a Starting Number
This article begins with a subroutine you can run rather than with a list of rules. The subroutine prints a 3N+1 sequence. The number the sequence starts from is not written inside the subroutine. Instead, the caller supplies that number each time it calls the subroutine.
Keep one question in mind as you read: when the subroutine runs, where does its starting number come from, and at what point does it first have a value? Answering that question is the whole mechanism this article is about. A subroutine that takes parameters can operate on different inputs, and the way it receives those inputs determines how you read its behavior and how you find the source of a wrong result.
Reading the Parameter List in the Header
A parameterized subroutine declares a parameter list in its definition, and that list specifies the number and types of values a caller must supply. The list is the part of the header inside the parentheses. For the subroutine in this article, the header is shown below.
- static void: the subroutine's header, naming it print3NSequence
- int
startingValue: the parameter list, declaring exactly one parameter - int: the type a caller's value must match
startingValue: the parameter name the body uses
Read the parameter list as a contract. It says the subroutine expects exactly one parameter, and that the parameter is of type int. Any caller who wants to use this subroutine must supply one int value. The number and types of the parameters tell any caller what must be supplied, before the caller writes a single line of the call.
A parameter is a name declared in a subroutine's parameter list. The definition declares the name but does not give it a value. The value arrives from outside, through a call statement.
Two Calls, One Definition
A call statement names the subroutine and supplies a value inside its parentheses. The same definition can be called with different values, and each call produces output based on the value it supplied. The driver below is a small illustration written for this article. It calls the same subroutine twice, once with a literal and once with a variable. The body of the subroutine is not repeated, because it is the same as the definition shown earlier.
- Line 1 gives the variable K the value 17
- Line 2 passes the literal 17 as the argument
- Line 3 passes the current value of K as the argument
Tracing a Call Before the Body Runs
The subroutine definition declares a parameter but does not give it a value. When the computer executes a subroutine call statement, it first assigns the value supplied in the call to the parameter name. Only then does it execute the statements of the subroutine body. Because of this order, the parameter name is ready for use the moment the body begins.
The diagram shows the same path described in words. The caller's value moves to the parameter, and the parameter is then available to the body. The key point is timing. The assignment to the parameter happens before the first statement of the body, which is why the body can rely on the parameter already holding a value.
Why the Body Can Use the Parameter Like a Variable
Inside the body, the parameter name is used in the same way as an ordinary variable name. The body is written in terms of startingValue. It has no direct connection to the name K in the caller. Any variable in the caller could be passed, as long as its current value is what the call should use.
The name startingValue belongs to the subroutine, and the name K belongs to the caller. The body never sees K. It sees only the value that was assigned to startingValue when the call began.
Finding Where a Wrong Result Starts
When a result is wrong, trace the path in order. First, identify the value the call supplied. Second, check its assignment to the parameter. Third, find the body's first use of the parameter. The first of these three steps where the actual value differs from the expected value is where the problem begins. The diagram below lays out that sequence as a pipeline you can walk through.
Tracing a Surprising Output
A sequence that starts from the wrong number
A programmer intends print3NSequence(K) to print the sequence starting at 17, but the output begins at a different number. The call is written as print3NSequence(K), and an earlier line in the program changes K before the call.
- 1Identify the value the call supplied
The argument is K, so the value supplied is the current value of K at the moment of the call, not the value K held when it was first declared.
- 2Check the assignment to the parameter
The computer assigns that current value to
startingValuebefore the body runs. The assignment copies the value the call supplied, sostartingValuematches K at the moment of the call. - 3Check the body's first use of the parameter
The body reads
startingValue. Because the parameter already holds the supplied value, the body is working from the value that was current at the call, which is the wrong number here. - 4Locate the divergence
The expected value (17) and the actual value first differ at the supplied-value step. The subroutine's parameter mechanism behaves as described; the input it received was not the value the programmer expected.
The divergence is at the supplied value. The fix is to find the earlier line that changed K, not to look inside the body first.
Mistakes With Parameters
Common mistake Expecting the definition to supply the parameter's value
What it looks likeReading print3NSequence(int startingValue) and assuming startingValue already holds a number before any call is made.
Why it failsThe definition declares the parameter but does not give it a value. The value comes from the call statement.
Do this insteadLook at the call statement to find the value. The parameter has no value until the call assigns one.
Common mistake Assuming the body sees the caller's variable name
What it looks likeExpecting the body to refer to K when the call is print3NSequence(K).
Why it failsThe body uses the parameter name
startingValue. The name K exists only in the caller.Do this insteadTrace the value: K's current value is assigned to startingValue, and the body works with startingValue.
Common mistake Checking the body first when a result is wrong
Why it failsThe body is usually correct for the value it received. The first place the actual value can diverge is the supplied value or its assignment.
Common mistake Forgetting that the argument is read at the moment of the call
What it looks likeChanging K after a call and expecting the earlier output to reflect the new K.
Why it failsThe computer takes the current value of K when the call executes.
Do this insteadCheck what K holds at the exact line of the call, not at some earlier or later point.
Practice Tracing a Call
What do you think happens?
Given int K = 17; followed by print3NSequence(K);, what value does startingValue hold on the first line of the body?
Reveal answer
17
The computer takes the current value of K, which is 17, assigns it to startingValue at the moment of the call, and only then executes the body. The body's first line therefore sees startingValue holding 17.
Suppose the program sets K to 17, calls print3NSequence(K), then sets K to 5, and then calls print3NSequence(17). List the steps in order for the first call, naming the value supplied, the assignment to startingValue, and the body's first use. Then say which value startingValue holds on the second call's first body line, and what K holds at that point.
Hints
- Identify the exact moment each call's argument is read.
- For each call, the assignment to
startingValuehappens before the body's first line. - The second call uses a literal, so K's value does not matter for it.
Key Takeaways
- A parameter list in a subroutine's header tells a caller how many values to supply and of what types. For
print3NSequence(int startingValue), the caller must supply exactly one int. - The definition does not give the parameter a value. The call statement supplies the value, which is assigned to the parameter before the body executes.
- A call can supply a literal or a variable. With a variable, the current value of that variable at the moment of the call is what the parameter receives.
- When a result is wrong, trace the path in order: the supplied value, its assignment to the parameter, and the body's first use of it. The first step where actual differs from expected is where the problem begins.
- This source material for the lesson describes how parameters receive their values. It does not describe how a return value travels back to the caller, so that part of the title is not covered by this article's source.