Lesson 58 of 100
Arrays15 min5 checksPHP 8.5

Aggregate Records with array_reduce

Learn how array_reduce folds a list of records into one structured result, and why the initial accumulator matters.

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.

Example
<?php
$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";
Output
[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.

Example
<?php
$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";
Output
{"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.

Example
<?php
$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";
Output
{"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:

  • count is the number of orders.
  • total is the sum of all cents values.
  • max is the largest cents value.

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 null for empty input.
  • Forgetting to return the carry from the callback.
  • Returning a carry with a different shape on some paths.
  • Overwriting max with 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.

Before you start

Functions · Arrays · Types

You’ll practice
  • Compute structured totals using an explicit initial accumulator
  • Implement and test a deterministic PHP 8.4 function
Your task

Make summarizeOrders() pass all 5 checks. Run your code as often as you like, then check your answer.

exercise.phpSaved in this browserPHP 8.5
CONSOLE & CHECKS● Ready

Ready when you are

Your output will appear here.
Make it work. Then make it pass.

PHP loads on your first run. Each check starts fresh. Your code stays in this browser; clearing site data removes your progress. A check or the task looks wrong? Report a problem with this exercise.