Skip to content
Theory · Array functions · lesson 3 of 8

array_map, array_filter and array_reduce

In the previous lesson the attendant rearranged items right on the hooks. But often you need the original array intact and want a different one made from it: discounted prices, only the even numbers or a grand total. Three more people work in the cloakroom for that:

  • the tailor alters every item and hangs the copy on a new rack—that is array_map;
  • the checker takes the extra items away and leaves the rest on their old hooks—that is array_filter;
  • the cashier goes over all the items and names one number, such as the total cost—that is array_reduce.

A function you pass in tells each of them what exactly to do. Most often it is an arrow function fn: one line holding the rule.

array_map: a new value for each one

array_map calls your function for every value of the array and collects the answers into a new array of the same size. The function is the first argument, the array the second:

PHP
<?php

$prices = [100, 250, 40];
$doubled = array_map(fn($p) => $p * 2, $prices);
echo implode(' ', $doubled), "\n"; // 200 500 80
echo implode(' ', $prices), "\n"; // 100 250 40
$stock = ['pear' => 7, 'kiwi' => 9];
echo json_encode(array_map(fn($n) => $n + 1, $stock)), "\n"; // {"pear":8,"kiwi":10}

Let's go through it step by step:

  1. fn($p) => $p * 2 tells the tailor what to do with one value.
  2. array_map calls this function three times—for 100, 250 and 40—and puts the answers into a new array, $doubled.
  3. $prices has not changed: the tailor makes copies, and the old items stay where they were.
  4. The keys move into the new array: $stock had the keys 'pear' and 'kiwi', and the result has the same ones.

array_filter: the keys stay as they were

array_filter takes its arguments the other way round: the array first, then the function. A value stays in the result if the function returns a truthy value for it, and drops out if it returns a falsy value.

The main trap here is the keys. The values that remain keep their original keys: the checker takes the extra items away but does not renumber the hooks. So after a filter a list becomes an array with holes: its keys no longer run in a row.

Press "Next step" and watch the keys in $even and $list.

LiveKeys after array_filterstep 1 of 10 · frames: 1

A list of four numbers with the keys 0, 1, 2 and 3.

Watch the keys in $even and $list

The rule the widget shows:

  • array_filter keeps the values together with their keys: the even numbers of [5, 8, 1, 6] have the keys 1 and 3;
  • count does not see the holes: there are two elements, although the largest key is 3;
  • array_values takes the values alone and numbers them from 0, which gives a real list.

From the outside the holes are not visible at once:

PHP
$nums = [5, 8, 1, 6];
$even = array_filter($nums, fn($n) => $n % 2 === 0);
echo implode(' ', $even), "\n"; // 8 6
echo implode(' ', array_keys($even)), "\n"; // 1 3
echo json_encode($even), "\n"; // {"1":8,"3":6}
echo json_encode(array_values($even)), "\n"; // [8,6]

The first output line, 8 6, looks like an ordinary list, while array_keys shows the real keys, 1 3. json_encode writes the array with holes in curly braces: to JSON it is an object with the keys "1" and "3", not a list. If that reply goes to a browser, the JavaScript code gets an object instead of a list and can break.

Interviewers often ask this: "Why did an object show up in the JSON after array_filter?" The answer: the filter kept the original keys, and array_values is what renumbers them.

array_reduce: one value

array_reduce folds the whole array into one value. You pass it the array, a function of two parameters and an initial value. The function's first parameter is what has been accumulated so far, the second is the next element:

PHP
$cart = [120, 80, 50];
$total = array_reduce($cart, fn($sum, $p) => $sum + $p, 0);
echo $total, "\n"; // 250
echo array_sum($cart), "\n"; // 250

Let's see how the cashier counts:

  1. $sum starts from the third argument, 0.
  2. The first call gets $sum = 0 and $p = 120 and returns 120.
  3. That answer becomes $sum in the second call: 120 + 80 gives 200.
  4. The third call adds 200 + 50, and array_reduce returns 250.

This is where the cashier picture falls short. A cashier keeps the total in their head, but your function has no memory: array_reduce itself passes it the total every time, in the $sum parameter.

For a sum there is a ready-made function, array_sum. array_reduce is what you reach for when you need your own rule: a product, the longest word, a string of all the names.

The three functions take their arguments in different orders, and people often mix them up:

  • array_map—the function first, then the array;
  • array_filter—the array first, then the function;
  • array_reduce—the array, the function and the initial value.

Common mistake

After a filter, the first element is read by index 0:

PHP
$scores = [3, 9, 4, 10];
$high = array_filter($scores, fn($s) => $s > 5);
echo $high[0], "\n"; // ✗
TEXT
Warning: Undefined array key 0

The number 3 with the key 0 did not pass the filter, so $high has no key 0: it holds only the keys 1 and 3. PHP shows a warning, and echo prints an empty line. The fix is to renumber the result: $high = array_values(array_filter($scores, fn($s) => $s > 5));, and then $high[0] is 9.

Key points

  • array_map returns a new array of the same size with the same keys and leaves the original alone.
  • array_filter keeps the values for which the function returns a truthy value, together with their original keys—the list gets holes.
  • array_values numbers the values again from 0; without it json_encode writes an array with holes as an object.
  • array_reduce passes each call's result into the next one, starting from the initial value, and returns one value.