PHP Array Functions
PHP ships with more than 80 built-in functions for working with arrays: sorting them, searching them, transforming every element, combining multiple arrays into one, and reshaping data for output. Instead of writing a manual loop for every operation, you reach for a purpose-built function like array_map(), array_filter(), or usort() that is implemented in C and runs faster and more predictably than hand-rolled PHP. Mastering these functions is one of the highest-leverage skills in PHP, because arrays show up everywhere in real programs, and the array functions are how you reshape that data into what your program actually needs.
Overview: How PHP Array Functions Work
A PHP array is not a simple list in memory the way it is in C. Internally, every PHP array is a HashTable, an ordered structure maintained by the Zend Engine that combines a hash map (for O(1) key lookups) with a doubly linked list (to preserve insertion order). This is why foreach always visits elements in the order they were added, even for string-keyed associative arrays, and why looking up $array['key'] is fast regardless of how many elements the array holds.
Array functions fall into a few natural families:
- Transform – produce a new array by applying logic to each element:
array_map(),array_filter(),array_reduce(),array_walk(). - Sort – reorder an array in place:
sort(),rsort(),asort(),arsort(),ksort(),krsort(), and their callback-driven cousinsusort(),uasort(),uksort(). - Search – find values or keys:
in_array(),array_search(),array_key_exists(). - Combine – build a new array from several:
array_merge(),array_combine(),array_diff(),array_intersect(),array_unique(). - Reshape – extract or restructure a slice of data:
array_slice(),array_splice(),array_chunk(),array_column(),array_flip(),array_keys(),array_values().
A crucial detail: most array functions take the array by value and return a brand-new array, leaving the original untouched. PHP arrays use copy-on-write internally, so passing a large array into array_map() is cheap; PHP only duplicates the underlying HashTable the moment something actually tries to modify a copy while another reference still points to the original data. The sort functions, and functions like array_push(), array_pop(), and array_walk(), are the exception: they mutate the array in place, which is why you pass the variable itself, not the result of an expression, and why their return value is a boolean (success/failure) rather than the array.
Syntax
The most-used array functions share a similar shape: an array as the primary argument, and often a callable that PHP invokes once per element (or once per comparison, for sorting).
array_map(callable $callback, array $array, array ...$arrays): array
array_filter(array $array, ?callable $callback = null, int $mode = 0): array
array_reduce(array $array, callable $callback, mixed $initial = null): mixed
usort(array &$array, callable $callback): bool
array_merge(array ...$arrays): array
array_search(mixed $needle, array $haystack, bool $strict = false): int|string|false
| Category | Common functions | Mutates the original array? |
|---|---|---|
| Transform | array_map(), array_filter(), array_reduce(), array_walk() |
No, except array_walk() when its callback takes the value by reference |
| Sort | sort(), asort(), ksort(), usort(), uasort(), uksort() |
Yes, in place, returns bool |
| Search | in_array(), array_search(), array_key_exists() |
No |
| Combine | array_merge(), array_combine(), array_diff(), array_unique() |
No, returns a new array |
| Reshape | array_slice(), array_splice(), array_chunk(), array_column() |
array_splice() mutates in place; the others return a new array |
The callable argument can be a named function ('strlen'), a first-class callable (strlen(...)), a closure, or, most commonly in modern PHP, an arrow function (fn($x) => ...), which automatically captures variables from the surrounding scope by value.
Examples
Example 1: Sorting an array of records with usort()
<?php
$products = [
['name' => 'Laptop', 'price' => 1200],
['name' => 'Mouse', 'price' => 25],
['name' => 'Monitor', 'price' => 300],
];
usort($products, fn(array $a, array $b) => $a['price'] <=> $b['price']);
foreach ($products as $product) {
echo $product['name'] . ': $' . $product['price'] . PHP_EOL;
}
Output:
Mouse: $25
Monitor: $300
Laptop: $1200
The spaceship operator <=> returns -1, 0, or 1 depending on whether the left value is less than, equal to, or greater than the right value, which is exactly the contract usort()‘s callback needs. Because usort() takes its array argument by reference, $products itself is reordered; there is no return value to reassign.
Example 2: Chaining array_map(), array_filter(), and array_reduce()
<?php
$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
$evenSquares = array_filter(
array_map(fn(int $n) => $n ** 2, $numbers),
fn(int $n) => $n % 2 === 0
);
$sum = array_reduce($evenSquares, fn(int $carry, int $n) => $carry + $n, 0);
echo "Even squares: " . implode(', ', $evenSquares) . PHP_EOL;
echo "Sum: $sum" . PHP_EOL;
Output:
Even squares: 4, 16, 36, 64, 100
Sum: 220
This is the classic map-filter-reduce pipeline: array_map() squares every number, array_filter() keeps only the even results, and array_reduce() folds the remaining values down to a single total, starting from the initial accumulator 0. Note that array_filter() preserves the original array keys, which is why some developers wrap the result in array_values() if they need a clean, re-indexed list afterward.
Example 3: Merging and searching associative arrays
<?php
$defaults = ['theme' => 'light', 'language' => 'en', 'notifications' => true];
$userPrefs = ['language' => 'fr', 'timezone' => 'UTC+1'];
$settings = array_merge($defaults, $userPrefs);
$key = array_search('fr', $settings, true);
echo "Merged settings:" . PHP_EOL;
foreach ($settings as $name => $value) {
$printable = is_bool($value) ? ($value ? 'true' : 'false') : $value;
echo " $name => $printable" . PHP_EOL;
}
echo "Found 'fr' at key: $key" . PHP_EOL;
if (in_array('UTC+1', $settings, true)) {
echo "Timezone is set" . PHP_EOL;
} else {
echo "No timezone" . PHP_EOL;
}
Output:
Merged settings:
theme => light
language => fr
notifications => true
timezone => UTC+1
Found 'fr' at key: language
Timezone is set
array_merge() combines the two arrays, and for string keys the later array’s value wins while keeping the key’s original position, which is why language stays in second place but its value becomes 'fr'. Passing true as the third argument to array_search() and in_array() enables strict comparison, which you should almost always do (more on why in Common Mistakes).
How It Works Step by Step: Under the Hood
<?php
function addTax(array $prices, float $rate): array
{
array_walk($prices, function (float &$price) use ($rate) {
$price = round($price * (1 + $rate), 2);
});
return $prices;
}
$original = [10.00, 25.50, 100.00];
$withTax = addTax($original, 0.08);
echo "Original: " . implode(', ', $original) . PHP_EOL;
echo "With tax: " . implode(', ', $withTax) . PHP_EOL;
Output:
Original: 10, 25.5, 100
With tax: 10.8, 27.54, 108
Walking through what actually happens:
- When
$originalis passed intoaddTax(), PHP does not copy the array’s data yet. Both$originaland the function’s local$pricespoint at the same underlying HashTable, and its reference count goes up. - Inside
array_walk(), the callback declares its parameter asfloat &$price, meaning it receives each element by reference. The instant PHP is about to write through that reference, it notices the HashTable is shared (refcount > 1) and performs a copy-on-write: it duplicates the array so$pricesnow owns its own independent copy. - Every element in that new copy is multiplied by
1 + $rateand rounded, mutating only the local copy. - The function returns the modified copy, which is assigned to
$withTax.$originalwas never touched, which is why it still prints its starting values.
This copy-on-write behavior is why passing arrays around in PHP is cheap by default, and why the language reserves the & reference syntax specifically for the places where you intend to mutate the caller’s data.
Common Mistakes
Mistake 1: Using sort() on an associative array
<?php
$scores = ['alice' => 90, 'bob' => 75, 'carol' => 88];
sort($scores);
print_r($scores);
Output:
Array
(
[0] => 75
[1] => 88
[2] => 90
)
sort() reindexes the array with fresh integer keys starting at 0, which silently destroys the 'alice', 'bob', and 'carol' keys. If you need to keep the association between names and scores while sorting by value, use asort() (ascending) or arsort() (descending) instead, since both preserve the original keys.
<?php
$scores = ['alice' => 90, 'bob' => 75, 'carol' => 88];
arsort($scores);
print_r($scores);
Output:
Array
(
[alice] => 90
[carol] => 88
[bob] => 75
)
Mistake 2: Loose truthiness checks on array_search()
<?php
$fruits = ['apple', 'banana', 'cherry'];
$position = array_search('apple', $fruits);
if (!$position) {
echo 'Not found';
} else {
echo "Found at position $position";
}
Output:
Not found
This is wrong, and it is one of the most common PHP bugs. 'apple' is genuinely found at index 0, but 0 is falsy in PHP, so !$position evaluates to true and the code reports failure. array_search() and in_array() can both return values that look falsy (0, '', null is not returned but 0 often is), so you must compare the result to false with the strict === operator instead of relying on truthiness.
<?php
$fruits = ['apple', 'banana', 'cherry'];
$position = array_search('apple', $fruits);
if ($position === false) {
echo 'Not found';
} else {
echo "Found at position $position";
}
Output:
Found at position 0
Best Practices
- Always pass
truefor thestrictparameter ofin_array()andarray_search()unless you specifically want PHP’s loose type-juggling comparison (which can, for example, treat'0e123'and'0e456'as equal). - Prefer
array_map()/array_filter()/array_reduce()over manualforeachloops when the logic is a simple transformation; it communicates intent and avoids stray mutable state. - Pick the sort function that matches your key requirements: use the plain family (
sort,rsort) when keys don’t matter, and thea-prefixed family (asort,arsort,uasort) when they do. - Remember that
array_filter()andarray_map()(with a single input array) preserve keys; callarray_values()afterward if you need a clean, re-indexed list. - When you use
foreachwith a reference (foreach ($arr as &$item)), alwaysunset($item)immediately after the loop, otherwise the dangling reference can silently corrupt the last element on a subsequent loop over the same variable. - Reach for
array_column()andarray_combine()to reshape arrays of records instead of hand-writing loops that build new arrays element by element. - Know the complexity:
isset($array[$key])andarray_key_exists()are O(1) hash lookups;in_array()andarray_search()are O(n) linear scans. For repeated lookups, flip a list into a keyed array first.
Practice Exercises
- Given
$words = ['banana', 'kiwi', 'fig', 'strawberry', 'pear'], useusort()to sort the array by string length, shortest first. Expected output when joined with commas:fig, kiwi, pear, banana, strawberry. - Given an array of user records like
[['id' => 1, 'active' => true], ['id' => 2, 'active' => false], ['id' => 3, 'active' => true]], usearray_filter()andarray_column()to produce a plain list of theidvalues for only the active users:[1, 3]. - Write a function
totalInventoryValue(array $items): floatthat takes an array of['price' => float, 'qty' => int]entries and returns the total value usingarray_reduce(), without using an explicitforeachloop.
Summary
- PHP arrays are ordered hash tables under the hood, which is why insertion order is preserved and key lookups are O(1).
- Transform functions like
array_map(),array_filter(), andarray_reduce()return new arrays and leave the original untouched. - Sort functions like
sort(),usort(), andasort()mutate the array in place and return a boolean, not the sorted array. - Choose the
a-prefixed ork-prefixed sort variants when you need to preserve keys during sorting. - Always use strict comparison (
=== false, or the strict third argument) when checking results fromarray_search()andin_array(), since0and other falsy return values can otherwise be misread as failure. - Copy-on-write means passing arrays around is cheap; PHP only duplicates the underlying data when a shared copy is actually modified.
