Formal Parameters and Passing Values to Subroutines
A subroutine can use local variables declared inside it, formal parameters that receive values from the caller, and variables defined outside it that remain accessible to other parts of the program.
Try it: Scope: Local, Enclosing, Global, Built-in
How Python looks up a name Local → Enclosing → Global → Built-in, why assigning to a name inside a function makes it local (and raises UnboundLocalError if it is read first), and what global, nonlocal and closures change.
How it works
- Before a function runs, Python marks every name assigned inside it as local to that function, unless it is declared global or nonlocal.
- Reading a name searches the local namespace, then enclosing functions, then the module's globals, then the built-ins; the first hit wins.
- A local name read before it has a value raises UnboundLocalError (Python does not fall back to the global).
- global x makes assignments go to the module namespace; nonlocal x makes them go to the nearest enclosing function.
- A function's local frame disappears when it returns, unless an inner function (a closure) still uses its variables.
Default run (10 steps): Ready: Local shadows global. Every name is looked up Local, then Enclosing, then Global, then Built-in. … Finished. Printed: inside: 99 / outside: 10.
Simplified: The lab's own small Python interpreter runs a fixed, hand-written script (you choose the variant and the values, never the code); nothing is executed as real Python. Every script was run in CPython 3.12 with the same inputs and the lab reproduces its output and error messages. Built-ins shows only the names the script used.
Loading the simulation…
Predicting What a Subroutine Changes
Before reading the rest of this article, try a short thought experiment. Suppose a caller has a whole-number variable holding the value 5 and passes it to a subroutine. Inside the subroutine, the parameter is increased by 10. Once the subroutine returns, what does the caller's variable hold? Decide on your answer before you continue.
What do you think happens?
After the subroutine returns, what value does the caller's variable hold?
Reveal answer
5, because the caller's variable keeps its original value.
For a primitive type, the formal parameter receives the caller's value when the subroutine is entered, and from then on it behaves much like a local variable. Changing it inside the subroutine does not reach back to the caller's variable.
The answer surprises many beginners because they assume that every name inside a subroutine is connected to the rest of the program in the same way. That assumption leads to predictable mistakes. This article sorts out which names a subroutine can reach, which of those names can influence the caller, and why the answer depends on where each name was originally declared.
Three Sources of Names Inside a Subroutine
A subroutine can work with three different kinds of variables. The first is a local variable, which the subroutine declares for its own use. The second is a formal parameter, which is the name in the subroutine's header that receives a value from whoever calls it. The third is a variable declared outside the subroutine, which the subroutine can still reach. The source describes this third kind as a static member variable declared outside the subroutine.
These three categories differ in two ways: where their names come from, and how far their effects travel. Parameters and locals are both created for the subroutine when it runs, while the outside variables already existed before the subroutine was called. That single difference, whether a name belongs to the subroutine or to the wider program, explains most of the behavior in the rest of this article.
A local variable is declared inside a subroutine and has no connection to the outside world. Its job is to support the internal workings of that subroutine, so it never affects the rest of the program. Programmers prefer local variables for temporary calculations because the subroutine can use them freely without creating side effects that other code must track.
A formal parameter is the mechanism for getting information into a subroutine. When the subroutine is called, the caller's values are placed into the parameter names, and the subroutine works with those names from then on. The parameter name is a fresh name inside the subroutine, even though it started with a value supplied by the caller.
A variable defined outside a subroutine exists independently of that subroutine and is accessible to other parts of the program. Relative to the subroutine, it is called a global variable. It can be used throughout the class in which it is defined and, if it is not declared private, in other classes as well.
Reusing One Subroutine Across a Run
Imagine a program that needs the same job done in three different places. You could copy the same lines of code three times, or you could write those lines once and let the rest of the program use them whenever they are needed. A subroutine is the second option: a block of code that can be called multiple times from different parts of a program.
The important word is called. A subroutine does not run just because it exists in a program. It runs when the program reaches a point where it calls it. Because the same block can be called more than once, a single piece of code can serve many moments in one run of the program. The diagram below traces a main program that calls the same subroutine twice; control leaves main, runs the subroutine, and returns to main each time.
Read the diagram from top to bottom. The subroutine's instructions are written once, but they execute twice, because each call starts a fresh run of that block and each return hands control back to the line of main that follows the call.
Why a Primitive Parameter Keeps Its Copy
The consequence for primitive types is direct. When the subroutine is called, the caller's value is dropped into the parameter name. Once execution of the subroutine begins, that parameter behaves much like a local variable. A change made to it inside the subroutine has no effect on the rest of the program, so the caller's variable keeps whatever value it had before the call.
The source adds an important qualification: the situation is more complicated for arrays and objects, which it promises to treat later. This article limits itself to primitive types, where the rule above holds cleanly. The diagram below shows the parameter as a separate box that receives a copy of the caller's value.
The changed value 15 exists only in the subroutine's parameter. The caller's x is still 5 when the subroutine returns, because the update was made to the parameter's own value, not to x.
Inside: 15
After: 5addTendeclares the formal parameter n, which receives a copy of the caller's value- n = n + 10 changes only the parameter inside the subroutine
- x in main is unaffected, so it still prints 5
How a Global Variable Carries Effects Outward
A global variable behaves differently from a parameter because it was never copied into the subroutine. It already existed outside the subroutine and is shared with other parts of the program. When the subroutine changes it, the change is visible wherever that variable is read afterward. The before-and-after diagram below shows a shared counter changing during one call.
count = 2- count is declared outside bump, so it is shared across the class
- bump takes no parameter; it reaches count directly
- Each call changes the same variable, so main sees the combined effect
Notice that main never passes count into bump. The effect travels outside the subroutine through the shared variable, which is exactly what makes global variables powerful and also what makes them easy to misuse.
Judging a Global Variable's Use
Using a global variable inside a subroutine makes that variable part of the subroutine's interface. The subroutine communicates with the rest of the program through the shared variable, not only through its parameter list. The source describes this as a kind of sneaky, back-door communication. It is less visible than communication through parameters, and it works against the goal of a black box whose interface is straightforward and easy to understand.
The source is careful not to forbid the practice. Global variables are not always bad inside subroutines. If the global variable is treated as part of the subroutine's interface, the communication is acceptable. Before using one, ask whether it is really necessary. If a value can be passed in as a parameter and returned or used directly, that is usually the more visible choice; a shared variable is reasonable when the design deliberately makes it part of the interface and the reason for sharing is clear.
Common mistake Assuming a parameter is the caller's variable under a new name
What it looks likeInside addTen(int n), writing n = n + 10 and expecting main's x to change
Why it failsA primitive parameter receives a copy of the value, so the update changes only the parameter
Do this insteadIf the caller must see a new value, the subroutine needs a different design, and the source's rules for arrays and objects must be considered separately
Common mistake Using a global variable without noticing it in the header
What it looks likeA subroutine that quietly increments a class-level counter while its parameter list is empty
Why it failsThe shared variable is an undeclared part of the interface, so other code can break when that variable changes
Do this insteadPass the value in as a parameter when practical, or make the shared use deliberate and documented
Common mistake Treating a subroutine as running once because it is written once
ThreeN2.java as a Working Example
The source points to a concrete program that uses subroutines: ThreeN2.java. It is introduced in Section 4.4 as an improved version of the 3N+1 program, and the source describes it as a program that uses subroutines and prints its output in neat columns. This makes it a good place to see the idea in an actual program rather than only in the abstract.
The source gives two reasons that ThreeN2.java relies on subroutines. The first is code organization: separating work into named blocks makes the program easier to read and manage. The second is output formatting: the program prints its results in neat columns, and the subroutines are part of how that formatting is achieved.
The source does not list the individual subroutines inside ThreeN2.java or show their code, so this article does not describe them in detail. When you open the file yourself, look for the places where the program calls a named block of code, and compare those calls with the places where it prints results. Each call you find is an instance of the reuse pattern from earlier in this article.
Practice Tracing a Call
A main program has an int variable total set to 7 and a static variable bonus set to 3 outside any subroutine. A subroutine addToTotal(int amount) sets amount = amount + 2 and then bonus = bonus + amount. main calls addToTotal(total) and then prints total and bonus. Predict what is printed for total and for bonus, then trace which change is local to the subroutine and which one reaches main.
Hints
- Ask whether amount is a copy of total or total itself
- Ask whether bonus was declared outside the subroutine
- Trace the value of amount after its update before using it in the bonus line
Key Takeaways
- A subroutine can use local variables, formal parameters, and variables declared outside it; the origin of each name determines how far its effects travel.
- For primitive types, a formal parameter receives a copy of the caller's value, so changes inside the subroutine do not affect the caller's variable.
- A global variable is shared, so changes made inside a subroutine are visible elsewhere and make that variable part of the subroutine's interface.
- A subroutine is a block of code that runs each time the program calls it, so one definition can serve many moments in a single run.
- Global variables are not forbidden, but they should be used only after asking whether they are really necessary.