Array unpacking and spread operator (...$array)
Concept
PHP 8.1 extended the spread operator ... to work with string-keyed arrays in function calls and array literals. Before 8.1, spreading was limited to numerically-indexed arrays. The spread operator in array context ([...$a, ...$b]) is a concise alternative to array_merge(), but with important semantic differences regarding key handling.
When spreading a numerically-indexed array into another array, keys are reindexed sequentially. When spreading an associative array (PHP 8.1+), the string keys are preserved, and later values with the same key overwrite earlier ones — identical to how array_merge() handles string keys. The key difference from array_merge() is that the + union operator preserves the first occurrence of a key, while array_merge() and spread both preserve the last.
Array unpacking in function arguments is the original use case: f(...$args) passes each element of $args as a separate positional argument. PHP 8.1 also added spread for string-keyed arrays in function calls, which maps to named arguments. This is powerful for building argument lists dynamically — for example, when wrapping a function call and forwarding arguments.
A subtle performance note: the spread operator in array literals ([...$a, ...$b]) creates a new array and copies all values. It is not O(1). For very large arrays, array_merge($a, $b) and [...$a, ...$b] have equivalent O(n+m) cost. The spread syntax is preferred for readability. Avoid spreading inside hot loops where $arr[] = $value or array_push() would suffice.
| Operation | String key behavior | Numeric key behavior |
|---|---|---|
[...$a, ...$b] | Last write wins | Reindexed from 0 |
array_merge($a, $b) | Last write wins | Reindexed from 0 |
$a + $b | First write wins | First write wins |
array_replace($a, $b) | Last write wins (recursive available) | Last write wins |
Code Example
<?php
declare(strict_types=1);
// ---- Numeric spread: reindexed ----
$first = [1, 2, 3];
$second = [4, 5, 6];
$merged = [...$first, ...$second];
// [1, 2, 3, 4, 5, 6] — keys 0..5
// ---- Compare: + operator keeps first value ----
$a = [1, 2, 3];
$b = [10, 20, 30, 40];
$plus = $a + $b; // [1, 2, 3, 40] — $a values win for keys 0,1,2
$spread = [...$a, ...$b]; // [1, 2, 3, 10, 20, 30, 40] — all 7 elements
// ---- Associative spread (PHP 8.1+) ----
$defaults = ['timeout' => 30, 'retries' => 3, 'verbose' => false];
$overrides = ['timeout' => 60, 'verbose' => true];
$config = [...$defaults, ...$overrides];
// ['timeout' => 60, 'retries' => 3, 'verbose' => true]
// overrides wins on duplicate string keys
// ---- Spreading into function call ----
function createUser(string $name, int $age, string $role = 'viewer'): array
{
return compact('name', 'age', 'role');
}
$args = ['name' => 'Alice', 'age' => 30, 'role' => 'admin'];
$user = createUser(...$args); // maps to named arguments in PHP 8.1+
// ---- Spread in the middle of a literal ----
$base = ['a' => 1, 'b' => 2];
$extra = ['c' => 3];
$full = ['prefix' => 0, ...$base, ...$extra, 'suffix' => 99];
// ['prefix' => 0, 'a' => 1, 'b' => 2, 'c' => 3, 'suffix' => 99]
// ---- Forwarding variadic arguments ----
function log(string $level, string ...$messages): void
{
foreach ($messages as $msg) {
echo "[$level] $msg\n";
}
}
$messages = ['Connected', 'Query executed', 'Response sent'];
log('INFO', ...$messages); // spreads $messages as positional args
// ---- Practical: merging config layers ----
$env = ['db_host' => 'prod-db', 'cache' => 'redis'];
$local = ['db_host' => 'localhost']; // local overrides env
$merged = [...$env, ...$local];
// ['db_host' => 'localhost', 'cache' => 'redis']Interview Q&A
Q: What is the difference between [...$a, ...$b] and $a + $b when both arrays share the same string keys?
The spread operator ([...$a, ...$b]) and array_merge() both implement "last write wins" — when $b has a key that already exists in $a, $b's value overwrites $a's value. The + (union) operator implements "first write wins" — keys already present in $a are not overridden by $b. For numeric keys, the distinction is even sharper: + preserves numeric keys as-is and skips duplicates, while spread reindexes all numeric keys to produce a contiguous sequence. Use + when you want defaults that can be overridden by specific values, and spread or array_merge() when the later array should take precedence.
Q: How does spreading a string-keyed array into a function call work in PHP 8.1, and what restriction existed before that version?
Before PHP 8.1, the spread operator in function calls only worked with numerically-indexed arrays, mapping elements to positional parameters by index. Spreading ['name' => 'Alice', 'age' => 30] into a function call would throw a TypeError because PHP could not map string keys to positional parameters. PHP 8.1 lifted this restriction by mapping string keys to named arguments. The function call f(...['name' => 'Alice', 'age' => 30]) is equivalent to f(name: 'Alice', age: 30). This enables powerful patterns like building argument bags from config arrays or forwarding named arguments through wrapper functions.
Q: In a performance-critical loop that builds a large array by merging small arrays on each iteration, should you use [...$result, ...$chunk] or array_push($result, ...$chunk)? Why?
Use array_push($result, ...$chunk) or foreach ($chunk as $v) { $result[] = $v; }. The expression $result = [...$result, ...$chunk] creates a completely new array on every iteration, copying all O(n) existing elements and all O(k) new elements. After 100 iterations of 100-element chunks, you have made 100 full array copies for a total of O(n²) work. array_push($result, ...$chunk) appends to the existing HashTable in O(k) amortized time per iteration, for a total of O(n) — a dramatic difference for large datasets. For merging many arrays, array_merge(...$chunks) called once after collecting all chunks is also O(n) and idiomatic.