How a Method Call Actually Works
Evaluate the arguments, copy them in, jump to the body
In a nutshell
When you write add(3 + 4, score * 2), three things happen in order:
- Java evaluates every argument expression all the way down to a literal value.
- Java copies those values, by position, into the method’s parameters. The parameter is a brand-new local variable that did not exist a moment ago.
- Java jumps into the body and walks it line by line. When the body finishes (or hits
return), control jumps back to the call site.
If you can run those three steps on paper without skipping any, you can predict the output of any one-method call. This protocol is also why a method that looks like it swaps two variables in main does not actually swap them: the method only sees copies.
Today in three sentences. A call evaluates its arguments first. Position binds, not names. Mutating a primitive parameter inside the method cannot reach the caller’s variable.
After this lesson, you will be able to:
- Apply the three-step call protocol to predict the output of any one-method call.
- State the pass-by-value rule for primitives and explain why it makes “swap” not work.
- Trace a parameter-mystery problem (caller passes literals, method mutates its parameters, calls happen in possibly-confusing order) using a step-by-step table.
From CSCD 110. Python passes by object reference: an integer parameter cannot be reassigned to affect the caller, but a list parameter can be mutated by the callee and the caller sees the change. Java is the same story for the primitives you have used so far (
int,double,boolean,char): the method only sees a copy. Reference types behave more like Python lists, but you do not meet those until arrays in Week 6.
The three-step call protocol
Apply this protocol the same way every time. The order matters.
Step 1: evaluate every argument to a literal value. Read left to right. Compute any arithmetic, any method calls inside the argument list, any variable lookups. Replace the expression with the value it produced.
Step 2: copy the values, by position, into the method’s parameters. The parameter list defines fresh local variables (one per parameter). Each parameter starts life initialized to the value of the corresponding argument. The first argument fills the first parameter, the second fills the second, and so on.
Step 3: jump into the body. Run the body line by line. When you hit a return (or fall off the end of a void method), jump back to the caller. If the method returned a value, the caller now holds that value at the call site.
Worked example. Suppose the method is:
public static int add(int a, int b) {
return a + b;
}
And the caller is:
int x = 10;
int y = 3;
int z = add(x + 1, y * 2);
System.out.println(z);
Apply the protocol to the call add(x + 1, y * 2).
| Step | What happens |
|---|---|
| 1 | x + 1 evaluates to 11. y * 2 evaluates to 6. |
| 2 | A new variable a is created with value 11. A new variable b is created with value 6. |
| 3 | The body runs. return a + b evaluates to 17. Control returns to the caller carrying 17. |
| post | z is assigned 17. The next line prints 17. |
The original x and y are untouched. They were used to compute the arguments, but the method itself sees only the values 11 and 6.
Common pitfall: assuming arguments are passed as-is. Some students think
add(x + 1, y * 2)“passes the expressionx + 1” into the method. It does not. Java fully evaluates the expression to a single value first, then passes that value. Insideadd, there is no trace of the expressionx + 1; there is only the number11.
Check your understanding. Apply the three-step protocol. What does this print?
public static int triple(int n) { return n * 3; } int a = 4; int result = triple(a + 1); System.out.println(result);Reveal answer
Step 1:
a + 1evaluates to5. Step 2: parameterngets the value5. Step 3: the body returns5 * 3=15. The variableainmainis still4. Output:15.
Position binds, not names
The parameter list does not match arguments by name. It matches by position. The first argument goes into the first parameter, regardless of what either is called.
public static void describe(int width, int height) {
System.out.println("width = " + width + ", height = " + height);
}
int height = 10;
int width = 20;
describe(height, width);
Output:
width = 10, height = 20
Read the call site carefully. The first argument is the variable named height, which holds the value 10. That value goes into the first parameter, which is named width. So inside the method, the parameter named width holds the value 10. The variable in main named height shares only its name with the value 10 that ended up in the parameter named width.
This is why naming arguments after the parameters helps no one. The compiler does not look at the name of the argument variable. It looks only at the order.
// These two calls are identical from the method's point of view:
describe(20, 10);
describe(width, height); // assuming width=20, height=10
Common pitfall: hoping that “matching names” will save you. Some students arrange their variable names to match the parameter names, hoping Java will do something smart. Java will not. The order is the only thing that matters. If you want to remember what each argument is, look at the method header, not the variable names.
Check your understanding. Given:
public static void mix(int x, int y) { System.out.println("x=" + x + ", y=" + y); } int y = 1; int x = 99; mix(y, x);What prints?
Reveal answer
x=1, y=99The first argument is the variable
y(value1). It binds to the first parameter, namedx. The second argument is the variablex(value99). It binds to the second parameter, namedy. Insidemix,xis1andyis99. Names in the caller and names in the callee are independent.
Pass by value: parameters are local copies
Step 2 of the protocol said “a new variable is created.” Take that literally. The parameter is a fresh local variable that did not exist before the call started. When the call ends, it stops existing. While it is alive, it holds a copy of the argument’s value, not a connection back to the caller’s variable.
This rule is called pass by value. For Java primitives (int, double, boolean, char, long, etc.), it has one important consequence: if you assign to a parameter inside the method, you change only the local copy. The caller’s variable is unaffected.
public static void tryToChange(int x) {
x = 999; // changes the local copy only
System.out.println("inside: x = " + x);
}
int value = 5;
tryToChange(value);
System.out.println("after: value = " + value);
Output:
inside: x = 999
after: value = 5
Inside the method, the parameter x got reassigned to 999. That happened to the parameter, not to value. When the method returned, x ceased to exist. Back in main, value is still 5. It was never changed.
The classic demonstration is a method that looks like it swaps two integers but does not.
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("inside swap: a=" + a + ", b=" + b);
}
int x = 1, y = 2;
swap(x, y);
System.out.println("after swap: x=" + x + ", y=" + y);
Output:
inside swap: a=2, b=1
after swap: x=1, y=2
Inside swap, the local copies named a and b get exchanged. But the caller’s variables x and y were never touched. When the method returns, the local copies vanish, and x is still 1, y still 2. There is no way for a method that takes two int parameters to swap two int variables in the caller. (You will see the trick that fixes this once arrays enter the picture in Week 6.)
Common pitfall: thinking the variable name carries through the call. Some students see
tryToChange(value)and read it as “sendvalueitself into the method.” That is not what happens. What goes into the method is the value held byvalueat that moment, copied into a new variable namedx. The connection betweenxandvalueends the instant the copy is made.
Check your understanding. What does this print?
public static void doubleIt(int n) { n = n * 2; } int score = 50; doubleIt(score); doubleIt(score); System.out.println(score);Reveal answer
Output:
50. Each call todoubleItmakes a new localninitialized to50(the current value ofscore). Inside the method,nbecomes100, but the caller’sscoreis never changed. Two calls do not “double twice” because the second call also starts fromscore = 50. Pass-by-value means the method is a one-way pipe in: values flow in, mutations stay local.
Check your understanding. Could a method
boost(int x)ever cause the caller’sintvariable to change? (Setting asidereturn, which we cover next lesson.)Reveal answer
No. With primitive parameters and no return value, the method has no channel back to the caller. Anything it computes inside is lost when the method returns. The only way a method can communicate a primitive value back to the caller is by returning it, and the caller has to store the returned value to use it. (Reference types like arrays change this story, but not for primitives.)
Reading a parameter mystery
A parameter-mystery problem is a small program designed to look confusing on purpose. The pattern is:
- A short method takes two or three primitive parameters and prints something using both its parameters and reassigns one of them.
maincalls the method two or three times with arguments in possibly-surprising order, sometimes passing literals, sometimes passing variables that share names with the parameters.
The right way to solve a parameter mystery is to trace it on paper using a table. Do not try to read it in your head.
Here is a worked example.
public static void mystery(int x, int y) {
System.out.println(x + " " + y);
x = x + y;
System.out.println(x + " " + y);
}
public static void main(String[] args) {
int x = 1, y = 2;
mystery(x, y);
mystery(y, x);
mystery(x + y, x * y);
}
Build a table. One row per call. Columns: arg evaluation, parameter binding, each line printed by the method.
| Call | Step 1 (evaluate args) | Step 2 (bind params) | Body line 1 prints | After x = x + y |
Body line 2 prints |
|---|---|---|---|---|---|
mystery(x, y) |
x = 1, y = 2 |
x_param = 1, y_param = 2 |
1 2 |
x_param = 3 |
3 2 |
mystery(y, x) |
y = 2, x = 1 |
x_param = 2, y_param = 1 |
2 1 |
x_param = 3 |
3 1 |
mystery(x + y, x * y) |
1 + 2 = 3, 1 * 2 = 2 |
x_param = 3, y_param = 2 |
3 2 |
x_param = 5 |
5 2 |
So the program prints, in order:
1 2
3 2
2 1
3 1
3 2
5 2
Two things to notice. First, main’s x and y never change. Each call makes a fresh local x_param and y_param; the assignment x = x + y inside the method mutates the local copy, not the caller. Second, the parameter names happen to be x and y, the same letters as the caller’s variables. That is a deliberate trap. Inside the method body, x always means the parameter, not the caller’s variable. The parameter shadows the caller for the duration of the call.
The trace table is mechanical and reliable. Slow it down, write everything, and the answer falls out.
Common pitfall: trying to trace in your head when the parameter names match the caller’s variable names. Confusion is guaranteed. Always rename the parameters in your trace table (
x_param,y_param) so you do not mix them up with the caller’sxandy.
Check your understanding. Trace this. What does it print?
public static void f(int a, int b) { a = a * 2; System.out.println(a + " " + b); } public static void main(String[] args) { int a = 3, b = 5; f(a, b); f(b, a); System.out.println(a + " " + b); }Reveal answer
Call Bind After a = a * 2f(a, b)a_param = 3,b_param = 5a_param = 66 5f(b, a)a_param = 5,b_param = 3a_param = 1010 3Then the final
printlninmainuses the caller’saandb, which are unchanged at3and5. Output:6 5 10 3 3 5
Wrap up and what’s next
Recap.
- The three-step call protocol: evaluate every argument to a literal, copy by position into a fresh local parameter, jump into the body.
- Position binds, not names. The compiler matches arguments to parameters by order alone.
- Pass-by-value for primitives means the parameter is a copy. Mutating the parameter cannot change the caller’s variable.
- A parameter mystery is a tracing exercise. Always use a table. Always rename the parameter columns to keep them straight from the caller.
What you can do now. Predict the output of any one-method program where the method takes primitive parameters, possibly mutates them, and prints. The same trace technique handles two-call and three-call programs without any new ideas.
Next up: Return Values, void, and Composition. Now that you know the call goes one way (in), the next question is how it comes back (out). You will pick return types, store the value at the call site, learn the rule for void, and see methods compose with each other (println(square(read()))).
Related resources
- Reges & Stepp, Building Java Programs, Chapter 3 sections 3.2 and 3.3 cover the call mechanics with worked traces.
- The parameter-mystery problem set at PracticeIt (free, no account required) is the same shape as the in-class quiz format.