Array performance — the cost of in_array on large arrays
Intermediate5 min read·php-15-006
performanceinterview
Concept
PHP array operations have different performance characteristics. Built-in functions (implemented in C) are almost always faster than equivalent PHP loops. Understanding when to use generators instead of arrays, and the cost of sorting, is key to efficient PHP code.
Key performance facts:
- Built-in functions beat custom loops:
array_map,array_filter,array_reduce,array_sum,array_uniqueare C-level. Equivalent pure PHP loops are slower. array_mapwith no callback — fastest cast:array_map(null, $a, $b)interleaves two arrays (zip). For single-array transformations, it's marginally slower thanforeachbecause it creates a new array.- Sorting:
usort()calls the comparison callback for every comparison — O(n log n) callback invocations. For simple property sorts,array_multisort()with extracted columns or Schwartzian transform (sort precomputed keys) avoids repeated expensive extraction. in_arrayvsisset:in_array($v, $haystack)is O(n) — scans the whole array.isset($lookup[$v])is O(1) — hash table lookup. For repeated membership checks against the same set, build a lookup array first.count()is O(1): PHP arrays track their length. No scanning required.- Unpacking
...$array: Spread operator on array arguments avoidscall_user_func_array()overhead.
Code Example
php
<?php
declare(strict_types=1);
$users = [
['id' => 1, 'name' => 'Alice', 'score' => 95],
['id' => 2, 'name' => 'Bob', 'score' => 87],
// ... 10,000 more
];
// O(n) for each check — SLOW for repeated lookups
$bannedIds = [3, 7, 42, 99];
foreach ($users as $user) {
if (in_array($user['id'], $bannedIds)) { // O(n) scan each time
// ...
}
}
// O(1) per check — FAST — build lookup once
$bannedLookup = array_flip($bannedIds); // [3 => 0, 7 => 1, 42 => 2, 99 => 3]
foreach ($users as $user) {
if (isset($bannedLookup[$user['id']])) { // O(1) hash lookup
// ...
}
}
// Sorting — Schwartzian transform (decorate-sort-undecorate)
// Extract sort key once, sort by key, restore original
$scored = array_map(fn($u) => [$u['score'], $u], $users); // pair key with data
usort($scored, fn($a, $b) => $b[0] <=> $a[0]); // sort by precomputed key
$sorted = array_column($scored, 1); // extract original items
// Much faster than:
// usort($users, fn($a, $b) => $b['score'] <=> $a['score']);
// for cases where extracting the key is expensive (e.g., method call)
// array_map vs foreach for side effects
// array_map — intended for TRANSFORMATION (returns new array)
$names = array_map(fn($u) => $u['name'], $users);
// foreach — for SIDE EFFECTS (sending emails, updating DB)
foreach ($users as $user) {
sendWelcomeEmail($user); // don't use array_map for this
}
// array_column — extract a column in one C call
$names = array_column($users, 'name'); // ['Alice', 'Bob', ...]
$byId = array_column($users, null, 'id'); // ['1' => [user1], '2' => [user2], ...]
$scores = array_column($users, 'score', 'id'); // ['1' => 95, '2' => 87, ...]
// count() is O(1) — safe in loop conditions
for ($i = 0; $i < count($users); $i++) { // count() called once per iteration — fine
// PHP's count is O(1), but storing it in a variable is still slightly faster
}
$len = count($users);
for ($i = 0; $i < $len; $i++) { } // micro-optimization — rarely necessary