Base CS from zero
Parameters and return values
You can now trace the call stack as functions are called and returned. The next question is: how does data get into a function, and how does the result get back out?
A function that always works on the same fixed values is almost useless. Real functions take parameters — values provided by the caller at call time — and produce a return value that the caller can use. These two mechanisms are the interface between a function and the code that calls it, and they both operate through the stack frame.
After this lesson you can explain what a parameter is (a value copied into the new frame at call time), what a return value is (a value produced by the function and handed back to the caller), and trace a function call that takes parameters and returns a value, showing the frame contents at each step.
Parameters are declared in the function signature. When the caller issues the CALL,
the values it supplies (called arguments) are copied into the new frame before the
function body starts executing. Inside the function, parameters look and behave exactly
like local variables — they are named cells inside the frame. The difference is that
they are pre-populated with the caller’s values rather than being set by assignment.
In TypeScript syntax, function add(x: number, y: number): number declares two
parameters x and y. At the call site add(3, 5), the values 3 and 5 are the
arguments that get copied into the new frame’s x and y cells.
Return values work in the reverse direction. When the function body reaches a
return statement, it computes the value to return and makes it available to the caller.
In hardware, this is typically done by placing the return value in a CPU register (a
fast storage slot inside the CPU itself) before firing RET. The caller can then read
that register after RET restores the program counter.
The key insight: parameters pass data into a frame; the return value passes data out.
Both crossings happen at the frame boundary — the moment of CALL (in) and RET (out).
The function to trace: add(x, y) takes two numbers and returns their sum. The trace
shows add(3, 5), with the stack before the call, during execution, and after the return.
1
function add(x: number, y: number): number {
2
let result = x + y; // result is a local variable in add's frame
3
return result; // hands result back to the caller
4
}
5
6
function main(): void {
7
let total = add(3, 5); // caller: arguments 3 and 5 passed to add
8
// total now holds 8
9
}
- L1 x and y are parameters — they live in add's frame, pre-filled with the caller's arguments
- L2 result is a local variable — add's frame now holds x, y, and result
- L3 return: places the value of result in a register, then RET fires
- L7 add(3,5): arguments 3 and 5 are copied into add's new frame as x and y
- L8 after RET: the register holding 8 is read; total is assigned 8 in main's frame
Trace main() which calls add(3, 5). Each cell shows one frame’s contents.
1
function add(x: number, y: number): number {
2
let result = x + y;
3
return result;
4
}
5
6
function main(): void {
7
let total = add(3, 5);
8
}
Why this works
Why are parameters copied into the frame rather than shared with the caller? Because each
function call needs an independent workspace. If add could modify the caller’s 3 and
5, calling add twice with different arguments would be impossible to reason about.
Copying the values means the callee’s frame is entirely self-contained — the callee can
freely modify its x and y cells without changing anything in the caller’s frame. This
“copy-in” behaviour is called pass-by-value and it is the default in most languages
for primitive values like numbers.
function multiply(a: number, b: number): number { return a * b; } — How many parameters does multiply have?
multiply(4, 6) is called. What value does multiply return?
After multiply(4, 6) returns, how many stack frames are on the stack (assuming it was called from main and nothing else is active)?
function square(n: number): number { return n * n; } — square(7) is called. What value is placed in the return register?
function add(x: number, y: number): number { let r = x + y; return r; } — add's frame holds x, y, and r. How many named cells does add's frame contain?
When add(3, 5) is called, where do the values 3 and 5 live during add's execution?
Parameters are named cells in a function’s stack frame, pre-filled with the
arguments (values) the caller supplied at call time. Inside the function, parameters
behave exactly like local variables. The values are copied into the frame (pass-by-
value for primitives), so the callee has an independent workspace. A return value is
produced by the return statement: the value is placed in a CPU register, RET fires,
the frame is popped, and the caller reads the return value from the register. Parameters
carry data into the frame at CALL; the return value carries data out of the frame
at RET.