PHPPass by Value vs Reference

Pass by Value vs Reference

When you call a PHP function with an argument, what actually gets handed to the function depends on how the parameter is declared. The default behavior is pass by value: the function receives an independent copy of a scalar or array, and changes made inside the function have no effect on the caller's original variable. PHP also supports pass by reference, using an ampersand (&) in the function signature, which lets a function reach back and modify the caller's variable directly. Objects add a third wrinkle worth understanding on their own, because they behave differently from both plain scalars and reference parameters.

Pass by value: the default for scalars and arrays

Unless you say otherwise, every argument — a number, a string, a boolean, or an entire array — is copied when it is passed into a function. The function works on its own private copy; whatever it does to that copy is invisible to the caller once the function returns.

Modifying the parameter does not touch the caller's array

PHP
<?php
function addItem(array $list)
{
    $list[] = "new item";
    echo "Inside function: " . implode(", ", $list) . "\n";
}

$groceries = ["milk", "eggs"];
addItem($groceries);

echo "Outside function: " . implode(", ", $groceries);
Inside function: milk, eggs, new item
Outside function: milk, eggs

$groceries in the caller is completely unaffected by what addItem() did to its own $list copy. This is true even though arrays can be large — PHP uses a copy-on-write optimization internally so the copy is cheap until something actually modifies it, but from your code's point of view it behaves exactly as if a full copy were made immediately.

Pass by reference with &$param

Prefixing a parameter with & in the function signature switches that specific parameter to pass by reference. Instead of a copy, the function receives a direct alias to the caller's variable, so any change made to the parameter inside the function is visible outside it too, once the function returns.

&$list lets the function modify the caller's array in place

PHP
<?php
function addItemByRef(array &$list)
{
    $list[] = "new item";
}

$groceries = ["milk", "eggs"];
addItemByRef($groceries);

echo implode(", ", $groceries);
milk, eggs, new item

This is exactly the mechanism several built-in PHP functions rely on, such as sort(), array_push() (in a different form), and preg_match()'s $matches output parameter — they all declare their array parameter by reference so they can update it in place rather than requiring you to reassign a return value.

sort() is a real-world example of pass by reference

PHP
<?php
$scores = [42, 7, 19, 3];
sort($scores); // sort() takes its argument by reference internally

print_r($scores);
Array
(
    [0] => 3
    [1] => 7
    [2] => 19
    [3] => 42
)
A function that empties an array in place

Reference parameters are especially handy for "act in place" helper functions, where you want the caller's variable itself to end up changed rather than getting a new value back that has to be reassigned.

Draining a queue in place

PHP
<?php
function drainQueue(array &$queue): array
{
    $removed = $queue;
    $queue = [];
    return $removed;
}

$tasks = ["email", "invoice", "backup"];
$processed = drainQueue($tasks);

echo "Processed: " . implode(", ", $processed) . "\n";
echo "Remaining: " . (empty($tasks) ? "(none)" : implode(", ", $tasks));
Processed: email, invoice, backup
Remaining: (none)
Objects: handles, not true references

Objects are often described as "passed by reference" in PHP, but that phrase is technically imprecise and worth untangling. A variable that holds an object does not hold the object itself — it holds a small internal handle (an identifier PHP uses internally to find the object). When you pass that variable into a function without an &, PHP copies the handle, not the object. Both the caller's variable and the function's parameter now point at the exact same underlying object, so mutating a property through either one affects the object both variables refer to.

Mutating a property through the copied handle

PHP
<?php
class Counter
{
    public int $value = 0;
}

function increment(Counter $counter)
{
    $counter->value++; // mutates the shared object
}

$c = new Counter();
increment($c);
increment($c);

echo $c->value;
2

Now compare that to reassigning the parameter itself inside the function. Reassignment changes what the local parameter variable points to, but it does not reach back and change what the caller's variable points to — because the handle was copied by value, just like a scalar would be.

Reassigning the parameter does not affect the caller's variable

PHP
<?php
class Counter
{
    public int $value = 0;
}

function replace(Counter $counter)
{
    $counter = new Counter(); // points the local copy at a brand new object
    $counter->value = 100;    // only affects the new object
}

$original = new Counter();
$original->value = 5;

replace($original);

echo $original->value; // still 5 - $original was never touched
5

The distinction matters: increment() changed the object everyone can see because it reached through the handle to modify a property. replace() only changed what its own local $counter variable pointed to — it never touched $original's handle at all.

Passing an object by reference with &

If you genuinely want a function to be able to reassign the caller's variable to a different object entirely — not just mutate the existing one — you still need the & reference operator, exactly like with scalars and arrays.

With &, reassignment IS visible to the caller

PHP
<?php
class Counter
{
    public int $value = 0;
}

function replaceByRef(Counter &$counter)
{
    $counter = new Counter();
    $counter->value = 100;
}

$original = new Counter();
$original->value = 5;

replaceByRef($original);

echo $original->value; // now 100 - $original itself was reassigned
100
  • Scalars (int, float, string, bool) and arrays are always copied on assignment and on being passed as arguments, unless the parameter uses &.

  • A variable holding an object stores a handle to that object, not the object itself; copying the variable copies the handle, not the object.

  • Mutating a property through any variable/parameter that shares a handle affects the one underlying object everywhere it is referenced.

  • Reassigning a parameter to a brand-new object only changes what that parameter points to, unless the parameter was declared with &.

  • Reference parameters (&$param) work the same way for objects as for scalars and arrays: they let the function replace what the caller's variable points to, not just mutate it.

Do not confuse object mutation with pass by reference
Seeing an object's properties change after being passed into a function does **not** mean PHP passed it "by reference" in the formal sense. It means the function mutated the one shared object through its copy of the handle. The real test for pass by reference is whether *reassigning* the parameter to a completely different value affects the caller — for objects without `&`, it does not.
Why references make code harder to trace
A function that silently modifies its arguments via `&$param` can be surprising to read at the call site, because nothing in `someFunction($data)` visually signals that `$data` might change. Many style guides prefer returning a new value over mutating in place for exactly this reason, reserving `&` for cases like sorting helpers where in-place mutation is the expected, well-documented behavior.
Tip
Default to pass by value and return a new result; reach for `&$param` only when in-place mutation is genuinely the clearest design (as with a sort-style helper) and document it clearly in the function name or a comment, since the reference is easy to miss at the call site.