skip to content

In PHP, what does a by-reference parameter such as &$param do, and what happens if you pass it a literal or an expression?

level: juniorimportance: should knowfreq 50%

answer

  1. & in the declaration, never at the call
  2. binds to the caller's variable
  3. literal: Error, could not be passed by reference
  4. call result: Notice, Only variables should be passed
  5. copy-on-write makes & useless for speed

basics

~20 s

A parameter declared &$param is bound to the caller's variable, so assignments inside the function change it. Only variables and returned references qualify: a literal throws Error, and a plain function result draws a notice with the change lost.

solid answer

~50 s

PHP passes arguments **by value** by default. Declaring `&$param` makes the parameter a **reference to the caller's variable**: `sort($rows)` and `preg_match($re, $s, $matches)` write into your variables this way. The `&` goes only in the declaration; the call looks normal. The argument must be something that can be referenced: a variable, an array element or property (created if missing), or a reference returned by a `function &f()`. A literal or assignment expression, like `bump(5)`, throws `Error: bump(): Argument #1 ($n) could not be passed by reference`. The result of an ordinary function call, like `end(explode(',', $s))`, raises `Notice: Only variables should be passed by reference` and the function works on a temporary, so its writes are lost. Using `&` to save copying is a myth: arrays are copy-on-write, so passing by value is cheap until someone writes.

code

php · 22 lines
php
<?php
declare(strict_types=1);

function addTax(float &$amount, float $rate = 0.2): void
{
    $amount *= 1 + $rate;
}

$total = 100.0;
addTax($total);
echo $total, "\n"; // 120

try {
    addTax(50.0);
} catch (Error $e) {
    // addTax(): Argument #1 ($amount) could not be passed by reference
    echo $e->getMessage(), "\n";
}

$csv = 'a,b,c';
$parts = explode(',', $csv);
echo end($parts), "\n"; // c, no notice: end() gets a variable

go deeper

for a junior

Recall that & goes in the function declaration, that it lets the function change the caller's variable, and that only variables can be passed.

for a middle

Explain the difference between the Error for literals and the Notice for call results, and why copy-on-write makes & pointless for speed.

for a senior

Show how you would refactor reference-heavy legacy functions into return values without breaking callers, and spot notices from end(explode()) style calls in logs.

for a principal

Set a team convention on output parameters and in-place mutation, weighing PHP's own by-reference APIs against readability and static analysis.

## By value is the default When PHP calls a function, each parameter normally receives a **copy of the argument's value**. Changing the parameter inside the function leaves the caller's variable untouched. For arrays and strings the copy is lazy (**copy-on-write**): caller and callee share one value until one of them modifies it, and only then is it duplicated. Passing a large array by value therefore costs nothing extra as long as the function only reads it. ## What `&` on a parameter changes Writing `&` before a parameter name, `function addTax(float &$amount)`, makes that parameter a **reference**: another name for the caller's variable. Every assignment to `$amount` inside the function is an assignment to the caller's `$total`. Key facts: - the `&` appears **only in the declaration**; the call is written normally, `addTax($total)`; - a by-reference parameter **may have a default**, which makes an optional output parameter possible, as in `preg_match(string $pattern, string $subject, &$matches = null, ...)`; - it works with **named arguments** too: `parse($input, errors: $errs)` binds `$errs`; - a **by-reference variadic**, `int &...$counters`, binds each passed variable; - functions can also **return** a reference, `function &find()`, which the caller must take with `=&`. That is rarely needed in modern code. Built-ins that rely on it include `sort()` and the other sorting functions (they sort the array you pass and return `true`), `array_push()`, `array_pop()`, `end()`, and `preg_match()` with its `$matches`. ## What may be passed | Argument passed to `function bump(&$n) { $n++; }` | Result | |---|---| | a variable: `bump($count)` | `$count` is incremented | | an undefined variable or array element: `bump($hits['home'])` | created as `null` without a warning, then incremented to `1` | | a literal: `bump(5)` | `Error: bump(): Argument #1 ($n) could not be passed by reference` | | an assignment expression: `bump($x = 5)` | same `Error` | | a user function's return value: `bump(nextId())` | `Notice: Only variables should be passed by reference`; runs on a temporary, change lost | | a reference returned by `function &counter()` | the returned reference is bound | The `Error` is thrown at runtime, so it can be caught, but it signals a bug. The notice case is the classic `end(explode(',', $csv))`: it prints the last element, but the notice shows up in logs, and the fix is to assign `explode()`'s result to a variable first. ## Objects are a different case An object variable holds a **handle** to the object, not the object itself. A by-value parameter receives a copy of that handle, so calling methods or setting properties on it affects the same object the caller sees, with no `&` involved. `&` on an object parameter is needed only if the function must make the **caller's variable** point to a different object, which is almost never good design. ## When to use it, and when not 1. **Use it** for output parameters that PHP's own API is built around (`preg_match()`'s `$matches`), or for in-place algorithms on a caller's array that must not return a copy. 2. **Avoid it for speed.** Copy-on-write already makes by-value passing cheap, and a reference makes the data flow harder to follow for readers and static analysers. 3. **Prefer returning values.** Return the new array, or a small value object, rather than mutating arguments; callers can see what changed from the assignment. 4. **Never write `&` at the call site.** `f(&$x)` is not valid syntax in current PHP; call-time pass-by-reference was removed long ago. ## Spotting reference bugs in review References fail quietly, so a few patterns are worth looking for: - a function that both **returns** a value and **modifies** a by-reference argument, where callers use only one of the two and miss the other effect; - an undefined variable passed to a by-reference parameter, which silently comes into existence as `null` instead of raising the usual undefined-variable warning; - a chain like `array_pop(explode(...))` or `end(array_keys(...))`, which triggers the notice and should assign the intermediate result to a variable first; - a by-reference parameter added "for performance" on a function that never writes to it, which can be turned back into a plain by-value parameter.

  • In PHP, why is declaring a large array parameter as &$rows usually not a performance win?
    Arrays are copy-on-write: a by-value parameter shares the caller's array until one side modifies it, so a function that only reads `$rows` never copies it. `&` saves nothing there, and it lets the function change the caller's data, which readers and static analysers then have to account for. It pays off only for deliberate in-place modification.
  • Can a PHP by-reference parameter be optional, and can it be passed by name?
    Yes to both. `function parse(string $s, ?array &$errors = null)` lets callers omit the output parameter, as `preg_match()` does with `&$matches = null`. With a named argument, `parse($input, errors: $errs)` binds `$errs`, while `parse($input, errors: [])` throws the same could-not-be-passed-by-reference Error as a positional literal.

By value hands the function a photocopy of your page; by reference hands over your notebook itself, so whatever it writes stays in your notebook. You cannot hand over a notebook for a number shouted across the room, which is why a literal cannot be passed by reference.

saying these in an interview costs you the question

  • Pass-by-reference is marked at the call site, as in foo(&$x).
  • Passing a literal to a by-reference parameter silently passes a copy.
  • end(explode(',', $s)) throws an Error because explode() returns a value.
  • Array parameters should be by reference to avoid copying on every call.
  • Objects need & for a function to change their properties.