9.1 Procedures, Parameters, Return Values, Nested Calls, and Scope
Key Takeaways
- A procedure header such as int newScore ( int a, int b, int old ) gives the return type, the name, and the typed parameters; void means nothing is returned.
- Arguments are matched to parameters by position, and a return statement ends the procedure immediately and sends a value back to the caller.
- In nested calls such as twice ( plusThree ( 4 ) ), the innermost call runs first and its result becomes the argument of the outer call.
- Assigning a new value to a primitive parameter inside a procedure does not change the caller's variable, but changing an element of an array parameter changes the caller's array.
- A local variable exists only inside its procedure or block; a local variable with the same name as a global variable shadows it without changing it.
What this competency asks
ETS asks you to understand how to write and call procedures with parameters and return values. Besides tracing, finding missing code, and identifying equivalent code, two skills are specific to procedures:
- Trace code when references to objects and arrays are passed to procedures.
- Trace code that includes nested procedure calls.
The data-types competency adds: distinguish between global and local scope.
Why procedures matter
A procedure (also called a function, method, or subroutine) packages a task behind a name. This gives you procedural abstraction: callers need to know what the procedure does, not how. Procedures avoid duplicated code, let a large program be built and tested in pieces, and make changes safer, because a fix in one place applies everywhere the procedure is called. ETS's sample dice-game question hinges on choosing the correct body for a procedure newScore ( dieOne, dieTwo, score ) whose purpose is described only by a table of results.
Procedure syntax in ETS pseudocode
int area ( int width, int height ) // returns an int
return width * height
end area
void printBanner ( String title ) // returns nothing
print "*** " + title + " ***"
end printBanner
| Term | Meaning | Example |
|---|---|---|
| Parameter (formal parameter) | A variable named in the header | width, height |
| Argument (actual parameter) | The value supplied in the call | area ( 3, 4 ): 3 and 4 |
| Return type | Type of the value sent back | int, or void for none |
| Return value | The value produced by return | 12 |
Arguments match parameters by position: the first argument goes to the first parameter, and so on. A return statement ends the procedure at once. Statements after it in the same path never run.
A value-returning procedure is used inside an expression: int a ← area ( 3, 4 ) + 1. A void procedure is called as a statement on its own: printBanner ( "Welcome" ).
Tracing a call
For each call:
- Evaluate the arguments.
- Create fresh parameter variables and copy the argument values into them.
- Run the body, tracking local variables.
- At
return, send the value back to the exact place the call appeared.
int addBonus ( int score )
score ← score + 5
return score
end addBonus
int s ← 80
int t ← addBonus ( s )
print s + " " + t
The parameter score starts as a copy of s (80) and becomes 85, which is returned into t. The caller's s is unchanged, so the output is 80 85.
Nested calls
When a call appears as an argument, evaluate from the inside out:
int twice ( int x )
return 2 * x
end twice
int plusThree ( int x )
return x + 3
end plusThree
print twice ( plusThree ( 4 ) ) + plusThree ( twice ( 4 ) )
plusThree ( 4 )= 7, sotwice ( 7 )= 14.twice ( 4 )= 8, soplusThree ( 8 )= 11.- The output is 14 + 11 = 25. The order of composition matters.
Procedures can also call other procedures from inside their bodies. Keep a stack of "who is waiting for whom". Each caller pauses until its callee returns, exactly as in recursion (Section 6.3).
Passing arrays and objects
A variable of a primitive type (int, double, boolean, char) holds its value directly. A variable that refers to an array or object holds a reference to it. When you pass it, the procedure receives a copy of the reference, so both names refer to the same array.
void update ( int[ ] arr, int x )
arr[0] ← arr[0] + 10 // changes the caller's array
x ← x + 10 // changes only the local copy
end update
int[ ] nums ← {1, 2, 3}
int y ← 5
update ( nums, y )
print nums[0] + " " + y
The output is 11 5. The array element changed because arr and nums refer to the same array. y did not change because x was a separate copy.
A subtler case, following the Java model: if a procedure assigns a whole new array to its parameter (arr ← new array), only the local reference changes, and the caller still refers to the original array. ETS's sample sort question relies on this sharing: a helper call swap ( arr, i, j ) exchanges two elements of the caller's array.
Some languages, such as C++ with & parameters, also offer pass-by-reference for primitive variables, which lets the procedure change the caller's variable directly. Unless a question says so, assume primitives are copied.
Scope and lifetime
Scope is the region of code where a name can be used. Lifetime is how long the variable exists while the program runs.
| Kind | Declared | Visible | Exists |
|---|---|---|---|
| Local | Inside a procedure or block (including loop headers) | Only there | While that procedure call or block runs |
| Parameter | In the procedure header | Only inside the procedure | For one call |
| Global | Outside all procedures | Everywhere, unless shadowed | For the whole program |
Shadowing: a local variable with the same name as a global variable hides the global inside that procedure.
int count ← 100 // global
int process ( )
int count ← 10 // local; shadows the global
for ( int k ← 1; k ≤ 3; k ← k + 1 )
count ← count + k
end for
return count
end process
int result ← process ( )
print count + " " + result
The local count becomes 10 + 1 + 2 + 3 = 16 and is returned. The global count is untouched. Output: 100 16. The declaration int count inside the procedure is what creates the new local variable. Without it, the procedure would be updating the global.
Why avoid globals? Any procedure can change a global variable, which creates hidden dependencies. A bug in one place can corrupt a value used somewhere else, and procedures become hard to test on their own. Passing values in as parameters and returning results keeps each procedure self-contained.
What is printed?
int twice ( int x )
return 2 * x
end twice
int plusThree ( int x )
return x + 3
end plusThree
print twice ( plusThree ( 4 ) ) + plusThree ( twice ( 4 ) )
What is printed?
void update ( int[ ] arr, int x )
arr[0] ← arr[0] + 10
x ← x + 10
end update
int[ ] nums ← {1, 2, 3}
int y ← 5
update ( nums, y )
print nums[0] + " " + y
What is printed?
int count ← 100
int process ( )
int count ← 10
for ( int k ← 1; k ≤ 3; k ← k + 1 )
count ← count + k
end for
return count
end process
int result ← process ( )
print count + " " + result
What is printed?
int addBonus ( int score )
score ← score + 5
return score
end addBonus
int s ← 80
int t ← addBonus ( s )
print s + " " + t