Parameter Passing Mechanisms: Call-by-Value, Call-by-Reference, and Call-by-Name
Parameter passing mechanisms define how arguments provided at a function call are bound to the corresponding parameters inside the callee. This binding choice directly affects (i) whether the callee can observe mutations, (ii) how much memory is needed, and (iii) how names in the caller relate to names in the callee.
Key terms you’ll see:
- Actual argument
- Formal parameter
- Aliasing
- Evaluation strategy
Parameter Passing (call-by-value, call-by-reference, call-by-name) — tutorial video
Big-picture classification
Common mechanisms include:
- Call-by-value
- Call-by-reference
- Call-by-name
- Call-by-need
Two meta-notes:
- Scope controls which identifiers are visible where (name binding).
- Parameter passing controls what values/storage those identifiers correspond to (binding of formals to actuals).
These are related but distinct: you can have lexical scope rules while still having different passing semantics.
Call-by-value: copy the value
Semantics
In call-by-value, the actual argument expression is evaluated (at the call site) and the resulting value is copied into the callee’s formal parameter.
Memory impact
- The callee gets its own storage for each parameter.
- If the parameter is a primitive, mutation inside the callee cannot affect the caller.
- If the parameter is a reference-like value (e.g., a pointer/address), then the pointed-to object may still be shared—so “value vs reference” depends on what the language treats as “the value.”
Scope impact
- Scope: parameter names are local to the callee (standard lexical scoping).
- Binding: the parameter name denotes the local copy.
Example (conceptual, language-agnostic)
Suppose:
- Caller has variable
x = 10 - Function
f(p)incrementsp
Then:
freceives a copy of10pbecomes11- caller
xremains10
Call-by-value execution (step-by-step)
- 1Step 1
Compute the value of the actual argument at the call site.
- 2Step 2
Create a new location for each formal parameter (conceptually a new binding).
- 3Step 3
Initialize the formal parameter from the copied value.
- 4Step 4
Reads/writes of the parameter affect only the callee’s local storage.
- 5Step 5
No parameter changes propagate back to the caller (for true value semantics).
Concrete illustration in a typical language style
C/C++-like value parameter:
- If
f(int p)is called withx, thenpis a local copy.
Even when your language has pass-by-value for primitives, be careful:
- In languages where “the value” is an address (e.g., passing a pointer by value), the callee can mutate the pointee, which may appear “like reference” mutation, but it’s still call-by-value of the address.
Call-by-reference: alias the caller’s storage
Semantics
In call-by-reference, the callee’s formal parameter refers to the caller’s variable storage location (or to something equivalent to it). So if the callee mutates the parameter, the caller observes the change.
Memory impact
- No per-parameter “copy” of the variable’s value is needed in the same way.
- Instead, the callee effectively holds an alias to the caller’s memory.
- This can reduce copying overhead but increases the risk of unintended side effects.
Scope impact
- Parameter name is still local to the callee, but its referent is not local-only storage.
- Because aliasing connects caller and callee, reasoning about scope changes: “local variable” writes may affect variables outside the callee.
Example (conceptual)
Caller:
x = 10
Call:
f(p)wherepis a reference tox
In f, do p = p + 1
Result:
xbecomes11
Call-by-reference execution (step-by-step)
- 1Step 1
Instead of copying, create an alias so the formal refers to the caller’s location.
- 2Step 2
Writes through the formal modify the caller’s variable (shared storage).
- 3Step 3
Caller observes changes because aliasing preserved linkage.
Call-by-name: pass expressions, not values
Semantics
In call-by-name, the actual argument is not evaluated at the call site. Instead, the callee receives a form of “thunk” that represents the expression plus the environment required to evaluate it.
Then:
- Every time the formal parameter is used, the corresponding expression is evaluated in the caller’s environment at that moment.
- This means effects depend on how many times the formal is used, and on side effects in the argument expression.
Memory impact
- No immediate copy of a value is made.
- You may allocate:
- a representation of the unevaluated expression (a closure/thunk),
- captured environment references so evaluation uses correct bindings.
- Repeated evaluation can be expensive: if the formal is used multiple times, the expression may be recomputed each time.
Scope impact
- Correct evaluation requires capturing the relevant environment for the expression.
- This interacts strongly with lexical scoping: the “meaning” of the expression inside the callee still depends on where variables are bound in the caller.
Canonical effect example: “argument used more than once”
Let:
- caller
x = 1 - function conceptually does something like:
return p + p(uses parameter twice) - actual argument is an expression with side effects, e.g.,
inc(x)orx++
Under call-by-name:
- Each use of
pmay trigger reevaluation, producing different results than call-by-value.
In many practical languages, true call-by-name is rare; however, optimization techniques and language features (e.g., lazy evaluation) can create similar observable behaviors.
Call-by-name execution (step-by-step)
- 1Step 1
Package the expression together with the needed environment.
- 2Step 2
Each access to the formal parameter causes evaluation of the expression.
- 3Step 3
If the parameter is referenced multiple times, the expression is re-evaluated each time.
- 4Step 4
Observed effects reflect evaluation count and side effects.
Implications for memory: what changes and what doesn’t
Call-by-value
- Parameter storage is local and typically contains the value.
- No aliasing with caller variables (for true value semantics and primitive values).
Call-by-reference
- Parameter storage acts as an alias.
- Less copying, more shared state, potentially more complex lifetime/side-effect reasoning.
Call-by-name
- Parameter storage is more like a suspended computation (thunk/closure).
- Potentially increased runtime cost due to repeated evaluation.
High-level comparison of parameter passing effects
Qualitative tendencies (not precise benchmarks).
Scope interactions: why names and storage must be distinguished
Even with the same scope rules (e.g., lexical scoping), parameter passing changes what the scoped name refers to:
- With call-by-value, the scoped parameter name refers to a local copy.
- With call-by-reference, the scoped parameter name refers to caller storage (aliasing).
- With call-by-name, the scoped parameter name refers to an expression evaluated in the caller’s environment.
Mermaid view: binding vs scope
Pro Tip: Think in terms of “what is shared?”
Ask: does the callee receive (1) a copied value, (2) an alias to caller storage, or (3) a delayed expression? That immediately predicts whether mutations and side effects can be observed by the caller.
Warning: “Reference-like” types can blur the intuition
In many languages, even with value parameters, passing an address/pointer by value can still mutate shared heap objects. The observable behavior depends on what “the value” actually contains (value vs address vs handle).
Common misconceptions and edge cases
Conceptual lifecycle of an argument under different passing modes
Evaluate now
Call-by-valueArgument expression evaluates at the call site, then is copied into the callee."
Bind to storage
Call-by-referenceFormals alias caller variables so mutations propagate outward."
Defer evaluation
Call-by-nameArgument expression is delayed and evaluated whenever the formal is used."
Defer + memoize
Call-by-need (optional)Evaluate at first use, cache the result for subsequent uses."
Parameter Passing Quick Checks
Knowledge Check
In call-by-value, the argument expression is typically ____.