01 · THE IDEAREAD → TRY → LEARN
Folding a list into one value
Many array tasks end with a single answer: a total, a lookup table, or a small summary. array_reduce() handles this by visiting each element in order and passing along a running value, called the carry or accumulator. The signature is array_reduce(array $array, callable $callback, mixed $initial = null): mixed.
The callback receives two arguments: the carry from the previous step and the current item. Whatever it returns becomes the carry for the next step. After the last element, the final carry is the return value of array_reduce().
Start with the simplest reduction
Summing numbers shows the mechanics. The initial value 0 is the first carry. Giving a different start, such as 10 for the product, shifts the whole result.
$numbers = [1, 2, 3, 4, 5];
$sum = array_reduce($numbers, fn(int $carry, int $item): int => $carry + $item, 0);
$product = array_reduce($numbers, fn(int $carry, int $item): int => $carry * $item, 10);
echo json_encode([$sum, $product]), "\n";[15,1200]
The sum is 15. The product starts at 10 and multiplies by 1 through 5, giving 1200.
Why the initial value matters
If you leave out initial, it defaults to null. For an empty array the callback never runs, so the result is null. That is rarely what a caller wants from a total. Passing an explicit initial value gives empty input a sensible answer.
$empty = [];
$withoutInitial = array_reduce($empty, fn($c, $i) => $c + $i);
$withInitial = array_reduce($empty, fn($c, $i) => $c + $i, 0);
echo json_encode(['without' => $withoutInitial, 'with' => $withInitial]), "\n";{"without":null,"with":0}
Because the type of the carry is whatever you choose, the first carry should already have the shape every later step expects.
The accumulator can be an array
The carry does not have to be a number. It can be an array that you update and return on each step. This is how you group records. Here the initial value is an empty array, and each sale adds to its category's running total. The null coalescing operator ?? supplies 0 the first time a category appears.
$sales = [
['category' => 'books', 'cents' => 1200],
['category' => 'games', 'cents' => 2500],
['category' => 'books', 'cents' => 800],
];
$totals = array_reduce($sales, function (array $carry, array $sale): array {
$category = $sale['category'];
$carry[$category] = ($carry[$category] ?? 0) + $sale['cents'];
return $carry;
}, []);
echo json_encode($totals, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"books":2000,"games":2500}
The callback receives the carry by value, so modifying $carry inside it does not change anything outside. The new array only matters because the callback returns it. Forgetting the return is the most common bug, since the next step would then receive null.
Several totals in one pass
A structured accumulator lets you compute several figures in a single traversal. Suppose each order record has a cents amount and you want the order count, the total, and the largest order. Start with ['count' => 0, 'total' => 0, 'max' => 0] and return a new array of the same shape on every step. Keeping the shape identical means the callback never has to check whether a key exists.
Choosing the start for max needs care. Using 0 is fine here because amounts are non-negative whole cents. If negative values were allowed, 0 could wrongly beat every real amount, and you would need a different strategy.
Exercise contract
Write summarizeOrders(array $orders): array. Each order is an associative array with an id string and a non-negative integer cents. Return an array with exactly the keys count, total and max, in that order:
countis the number of orders.totalis the sum of allcentsvalues.maxis the largestcentsvalue.
For an empty list, return zero for all three keys. Use array_reduce() with an explicit initial accumulator, and do not modify the input.
Common pitfalls
- Omitting the initial value and then getting
nullfor empty input. - Forgetting to return the carry from the callback.
- Returning a carry with a different shape on some paths.
- Overwriting
maxwith the current item instead of comparing it with the carry.
Recap
array_reduce() turns many items into one value by threading a carry through a callback. Choose the initial value deliberately, keep the carry's shape consistent, and always return it. When the carry is an array, one pass can produce groups or multiple statistics at once.
Functions · Arrays · Types
- Compute structured totals using an explicit initial accumulator
- Implement and test a deterministic PHP 8.4 function