In PHP, when would you use array_walk() or a foreach instead of chained array_map() and array_filter() calls on a large order export?
answer
- each helper returns a whole new array
- chains hold intermediate arrays at once
- array_walk mutates via &$value, returns true
- array_walk cannot add or remove elements
- one foreach pass, one result array
basics
~20 sEach array_map() or array_filter() call builds a complete new array, so a chain over a large export holds several full-size arrays in memory. A single foreach, or array_walk() when values are changed in place, does the work in one pass without intermediates.
solid answer
~40 sFunctional helpers are **eager**: `array_filter()` returns a new filtered array, `array_map()` a new mapped one, and a three-step chain over a 200 000-row export builds three full-size arrays, with the input and at least one intermediate alive at the same time, plus a callback call per element per step. A single `foreach` that filters, transforms and writes the result does one pass and allocates only the output. `array_walk(array &$array, callable $callback, mixed $arg)` is the in-place variant: the callback receives `$value` (declare it `&$value` to modify), the key and an optional extra argument, and `array_walk()` returns `true`, not the array. It may change values but must not add, remove or reorder elements. For inputs too large to hold at all, stream them instead of loading an array.
code
php · 24 lines<?php
$orders = [
['order_id' => 5012, 'total_cents' => 1850, 'status' => 'paid', 'slot' => ' 08:00 '],
];
// Chain: builds a filtered array, then a mapped array, then a list.
$lines = array_values(array_map(
fn (array $o): string => sprintf('%d:%d', $o['order_id'], $o['total_cents']),
array_filter($orders, fn (array $o): bool => $o['status'] === 'paid'),
));
// One pass: no intermediate arrays.
$lines = [];
foreach ($orders as $o) {
if ($o['status'] !== 'paid') {
continue;
}
$lines[] = sprintf('%d:%d', $o['order_id'], $o['total_cents']);
}
// In place: array_walk() returns true, not the array.
array_walk($orders, function (array &$o, int $i, string $tz): void {
$o['slot'] = trim($o['slot']) . " $tz";
}, 'Europe/Lisbon');go deeper
Recall that array_map() returns a new array while array_walk() changes the given array in place through a by-reference parameter and returns true.
Explain the callback arguments of array_walk(), why only values may change, and how chained helpers each allocate a new array.
Measure peak memory on large exports, replace multi-step chains with one pass where it matters, and avoid assigning array_walk()'s true result.
Set guidance on when readability of functional chains outweighs their memory cost, and when data volume calls for streaming or database-side processing.
## Eager helpers, whole arrays Every transform helper in PHP's array family is **eager**. It consumes an entire array and returns an entire new array: - `array_filter($rows, $keep)` allocates an array of the surviving rows. - `array_map($format, $rows)` allocates an array of the results. - `array_values()`, `array_column()`, `array_merge()` each allocate another. On small arrays this is irrelevant and the chain reads well. On a large export, say 200 000 order rows loaded for a nightly bakery report, each step materialises another array of up to the same length. The input must stay alive while the first step builds its output, that output stays alive while the next step runs, and so on, so peak memory is roughly the input plus one or two intermediates. Each step also calls a PHP callback once per element, so a three-step chain makes about three times as many function calls as a single loop. ## One loop instead of a chain The same work as a single `foreach`: 1. Read each row once. 2. Skip rows that fail the filter with `continue`. 3. Transform the row and append it to the result, or add it to a running total. | Aspect | Chained helpers | Single `foreach` | |---|---|---| | passes over the data | one per step | one | | intermediate arrays | one per step | none | | callback calls per row | one per step | none (inline code) | | key control | depends on each helper's key rules | explicit | | readability on small data | often better | fine | This is not an argument against `array_map()` in general. It is an argument for measuring, with `memory_get_peak_usage()` for example, when data is large, and for preferring a loop when a chain would materialise several big arrays. ## array_walk(): in-place modification `array_walk(array|object &$array, callable $callback, mixed $arg = UNKNOWN): true` visits every element of the array it was given **by reference** and calls `$callback($value, $key)` or, when `$arg` is supplied, `$callback($value, $key, $arg)`. - To change values, declare the callback's first parameter by reference: `function (&$row, $key) { ... }`. The changes land in the original array, so no second array is built. - The return value is always `true`. Code such as `$rows = array_walk($rows, ...)` replaces the data with `true`, a classic bug. - The callback may change values only. Adding, removing or reordering elements during the walk is documented as undefined behaviour. - Since PHP 8.0, declaring the key or the extra argument by reference emits a warning, because only the value can be modified. - A callback that requires more parameters than `array_walk()` provides throws `ArgumentCountError`. In practice `array_walk()` is useful for normalising values in place, such as trimming every string field or converting prices to cents, when the key is needed as context. A `foreach ($rows as &$row)` does the same job; if you use it, `unset($row)` afterwards so the reference does not linger. ## Choosing - **Small or medium arrays, transformation reads as a pipeline**: chained helpers are fine and often clearer. - **Large arrays or several steps**: one `foreach`, building only the final result. - **Modify values in place, need the key**: `array_walk()` with `&$value`, or a by-reference `foreach`. - **Data too large to hold in memory at all**: do not build the array; stream the rows lazily from the file or database cursor instead. ## What interviewers listen for They want to hear that helpers are eager and allocate, that key handling differs per helper, that `array_walk()` mutates and returns `true`, and that the right answer depends on the data size and on measuring, not on a blanket rule that loops are faster.
- What is wrong with $rows = array_walk($rows, fn (&$r) => ...);?`array_walk()` always returns `true`, so the assignment replaces the data with `true`. It modifies the array passed by reference and returns nothing useful. Call it as a statement and let the by-reference callback parameter carry the changes.
- Why does array_walk() warn when the callback takes the key by reference?Only values can be modified during a walk; keys and the array's structure must stay as they are. Since PHP 8.0, declaring the second or third callback parameter by reference emits a warning, because changing them could not have the effect the author intended.
saying these in an interview costs you the question
- array_map() and array_filter() process elements lazily, one at a time.
- array_walk() returns the modified array.
- array_walk() callbacks may safely unset elements while walking.
- Chaining three helpers costs the same memory as one foreach.
- Loops are always faster, so helpers should never be used.