skip to content

Subroutines and Procedures

How a call really works: the activation record that holds it, and what by value, by reference and by name each mean. It settles every 'is this pass by reference?' argument.

on this pageshow

questions

5

Under call by value versus call by reference, what exactly does a call copy when a routine is handed a sensor record to fill?

level: juniorimportance: must knowfreq 78%

answer

  1. ask what the call copied
  2. a value, or a location
  3. assignment is the deciding experiment
  4. mutation alone proves only sharing
  5. a copied reference is still by value

basics

~20 s

Call by value copies the argument's value into a fresh parameter slot, so assigning to the parameter cannot reach the caller. Call by reference copies the location of the caller's variable, so assigning to the parameter replaces the caller's variable.

solid answer

~50 s

The modes differ in one thing: what the call puts in the parameter's slot. **Call by value** copies the argument's value into a new slot in the callee's frame, so the parameter is a separate variable and an assignment to it dies with the call. **Call by reference** copies the *location* of the caller's variable into the slot, making the parameter a second name for that storage, so an assignment to it is visible to the caller the instant it happens. Mutation is not the test. If a variable holds a reference to a record and that reference is copied by value, the routine can still fill the record the caller sees — two routes to one record — but it cannot replace the caller's variable. The discriminating experiment is assigning a whole new record to the parameter and asking what the caller holds afterwards.

code

pseudocode · 8 lines
pseudocode
procedure fill(record)
    record.temperature = 21     // lands on the shared record
    record = makeRecord()       // rebinds this slot only
    record.temperature = 99     // written into the new record

r = makeRecord()
fill(r)
print r.temperature             // 21 - the rebind never reached the caller

go deeper

for a junior

Recall the one-line contrast: a value copied into a new slot versus the caller's storage location copied into it. Be ready to say what an assignment to the parameter does to the caller's variable in each case.

for a middle

Explain why mutating a field through a parameter proves only that two names reach one record. Walk the experiment that actually separates the modes: assign a fresh record to the parameter and report what the caller holds.

for a senior

Show the design consequence. A by-reference parameter grants a routine permission to replace the caller's variable, not just to fill a record, and it introduces aliasing that later readers must reason about at every write.

for a principal

Frame it as an interface policy. Decide when your codebase lets a callee replace a caller's variable at all, versus requiring results to be returned, and be able to argue the cost of aliasing against the cost of copying wide records.

A **parameter mode** is the rule that connects an argument written at the call site to the parameter named inside the routine. Take a concrete setting: a device driver calls a telemetry ingest routine once per sensor reading and hands it a record to fill. The argument is the driver's variable; the parameter is the routine's. What the call *copies into the parameter's slot* is the whole of the difference between the modes, and naming it settles every "is this pass by reference?" argument in one sentence. ## Call by value Under **call by value** the call evaluates the argument and copies the resulting **value** into a fresh slot in the callee's activation record. The parameter is a brand-new variable that merely begins life holding the same value. - Assigning to the parameter writes into that fresh slot and nowhere else. The caller's variable is untouched before, during and after the call. - The call pays for however large the value is. Copying a wide record costs more than copying a small scalar, but the mode is the same in both cases — nothing about call by value restricts it to small values. - The routine cannot replace the caller's variable. If it has a result to deliver, it returns one. ## Call by reference Under **call by reference** the call does not copy the argument's value at all. It copies the **location** of the caller's variable — the storage that variable names — into the parameter slot. The parameter becomes a second name for one piece of storage. - Assigning to the parameter assigns to the caller's variable, and the caller sees it at the moment of the assignment, not at the return. - Only something that *has* a location can be passed this way. A computed expression must be given temporary storage first. - Two parameters, or a parameter and a variable the routine can already reach, may end up naming the same storage. That is **aliasing**, and it is the standing cost of the mode: a write through one name changes what the other name reads. ## Where the confusion actually comes from Most of the argument is really about a third arrangement that is *neither* mode: **a value that happens to be a reference, passed by value**. The driver's variable does not store the record; it stores a reference to the record. The call copies that reference. Now two independent slots hold two copies of one reference, and both lead to the same record. | What the call copies into the slot | Assigning to the parameter | Mutating what the parameter leads to | |---|---|---| | the value itself | caller unaffected | nothing is shared to mutate | | a reference value, copied | caller unaffected — only the copy is rebound | caller sees it — one record, two routes | | the location of the caller's variable | the caller's variable is replaced | caller sees it | Rows two and three are indistinguishable as long as the routine only ever **mutates**. They separate the moment the routine **assigns a whole new record to the parameter**: with a copied reference the caller still holds the old record, while under call by reference the caller's variable now holds the new one. That assignment is the experiment that decides it, and it is what an interviewer is listening for when the question comes up. Languages differ in what they offer here. Some provide only the copy-the-value mode and obtain all shared mutation through reference values; others provide an explicit by-reference mode declared on the parameter or requested at the call site. The mechanism to name is identical either way — a slot received a value, or a slot received a location. ## Two further modes, same question The classical list has two more entries. **Copy-restore** copies the value in at the call and copies the parameter's final value back out at the return, so the caller sees one update, late. **Call by name** substitutes the argument expression itself and re-evaluates it at every use. Both are still answered by "what did the call copy?" — a value copied twice, or an unevaluated expression. ## How to answer it cleanly 1. Say what the parameter slot received: a value, or a location. 2. Say what an **assignment to the parameter** does to the caller's variable. That is the property the mode actually governs. 3. Only then discuss mutation, which is a property of the thing referred to, not of the mode — and say so explicitly, because conflating the two is the mistake the question exists to catch. The design consequence follows from step 1. If you want a routine to fill a record for its caller, you either hand it a route to a record that already exists, or you have it return a record. Reaching for a by-reference parameter is a decision to let the routine replace the caller's variable — a larger permission than "fill this in", and one worth granting deliberately.

  • If a language offers only call by value, how can a routine still hand a filled record back to its caller?
    Three ways, all of them still by value. It can return the record as the call's result. It can be handed a reference value that leads to storage the caller already owns and write through it. Or the caller can pass a location explicitly, as a value that denotes storage, and the routine writes through that. In each case the slot received a value; only what the value denotes differs.
  • Does passing by reference avoid copying, and is it therefore always cheaper?
    It copies a location instead of the value, which is a win for a wide record and a wash or a loss for a small one. Every access through the parameter then costs an indirection, and the mode admits aliasing, so the compiler can assume less about what a write touches. Cheaper at the call, potentially more expensive inside it.
  • What single experiment distinguishes call by reference from a reference value passed by value?
    Assign a whole new record to the parameter, then look at the caller's variable. Under call by reference the caller's variable now holds the new record. With a copied reference the caller still holds the old one, because only the callee's own slot was rebound. Mutating a field answers nothing — both arrangements show it.

Handing someone a photocopy of a page is call by value: whatever they scribble on it, your page is unchanged. Handing them a copy of the map to your filing drawer is different again — they can rearrange what is in the drawer, but tearing up their copy of the map leaves your drawer, and your own map, exactly where they were.

saying these in an interview costs you the question

  • Says the caller saw a change, so it must be by reference
  • Calls every record argument by reference because the record is shared
  • Believes call by value applies only to small scalars, never to records
  • Assumes by reference avoids all copying and is therefore always cheaper
  • Says assigning a fresh record to the parameter always reaches the caller
open as a page

What does the activation record pushed for one call to a telemetry ingest routine actually hold?

level: middleimportance: must knowfreq 60%

basics

~20 s

An activation record is the block of storage created for one call. It holds the return address, saved caller state such as the previous frame's base, the incoming parameters, the routine's locals, temporaries for part-finished expressions, and room for the result.

open as a page

A telemetry routine returns a reference to a record held in its own frame; why is that reference invalid after the return?

level: middleimportance: should knowfreq 50%

basics

~20 s

The frame is released at the return, so the storage the reference names is no longer reserved for that record and the next call may claim it. The reference is invalid from the return onwards, even though reading through it often still succeeds.

open as a page

A routine that recurses once per queued sensor reading crashes on large batches; what makes deep recursion fail?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Every live call holds a whole frame, and frames are pushed into one bounded region reserved for the running thread. Depth multiplied by frame size outgrows that region, and the overrun is detected as a fault rather than handled gracefully.

open as a page

Under copy-restore parameter passing, when does the result differ from call by reference for the same call?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

They diverge whenever a second route reaches the same storage during the call. Copy-restore works on a private copy and writes back once at the return, so aliased writes are lost or overwritten and timing differs; call by reference makes every write land immediately.

open as a page