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 cousins usort(), 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 $original is passed into addTax(), PHP does not copy the array’s data yet. Both $original and the function’s local $prices point at the same underlying HashTable, and its reference count goes up.
  • Inside array_walk(), the callback declares its parameter as float &$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 $prices now owns its own independent copy.
  • Every element in that new copy is multiplied by 1 + $rate and rounded, mutating only the local copy.
  • The function returns the modified copy, which is assigned to $withTax. $original was 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 true for the strict parameter of in_array() and array_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 manual foreach loops 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 the a-prefixed family (asort, arsort, uasort) when they do.
  • Remember that array_filter() and array_map() (with a single input array) preserve keys; call array_values() afterward if you need a clean, re-indexed list.
  • When you use foreach with a reference (foreach ($arr as &$item)), always unset($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() and array_combine() to reshape arrays of records instead of hand-writing loops that build new arrays element by element.
  • Know the complexity: isset($array[$key]) and array_key_exists() are O(1) hash lookups; in_array() and array_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'], use usort() 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]], use array_filter() and array_column() to produce a plain list of the id values for only the active users: [1, 3].
  • Write a function totalInventoryValue(array $items): float that takes an array of ['price' => float, 'qty' => int] entries and returns the total value using array_reduce(), without using an explicit foreach loop.

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(), and array_reduce() return new arrays and leave the original untouched.
  • Sort functions like sort(), usort(), and asort() mutate the array in place and return a boolean, not the sorted array.
  • Choose the a-prefixed or k-prefixed sort variants when you need to preserve keys during sorting.
  • Always use strict comparison (=== false, or the strict third argument) when checking results from array_search() and in_array(), since 0 and 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.