0

Array searching: in_array, array_search, array_key_exists, isset

Beginner5 min read·php-04-008
interview

Concept

PHP provides four primary functions for searching arrays: in_array(), array_search(), array_key_exists(), and isset(). They are deceptively similar but have critical behavioral differences that catch developers off guard, especially around type coercion and null handling.

in_array($needle, $haystack) performs a linear scan of the array values and returns a boolean. Without the strict third argument (true), it uses loose comparison (==), which means in_array(0, ['foo', 'bar']) returns true in PHP 7 because 0 == 'foo' under loose comparison. In PHP 8.0, the comparison behavior changed: 0 == 'non-numeric-string' now returns false (previously true), fixing the most glaring of these bugs. Always pass true as the third argument: in_array($needle, $haystack, true). The time complexity is O(n) — for large arrays used as lookup tables, convert to a flipped associative array and use isset() instead.

array_search() is identical to in_array() but returns the key of the first matching value (or false if not found). The same loose-vs-strict caveat applies. Because it returns false on failure and 0 is a valid key, always use strict comparison: if (false !== $key = array_search($val, $arr, true)).

array_key_exists() checks for key existence and correctly returns true when the key's value is null. isset($arr[$key]) returns false for both missing keys and null values — it is a language construct, not a function, so it is faster but has different semantics. Use array_key_exists() when null is a valid value. Use isset() when you just need "is there a usable value at this key?"

A practical performance pattern: for a set of 1,000+ values that you search repeatedly, in_array() in a loop is O(n×m). Converting to a set with array_flip() enables O(1) isset() lookups at the cost of one O(n) flip operation.

Code Example

php
<?php
declare(strict_types=1);

// ---- in_array strict vs loose ----
$list = ['1', '2', '3']; // string values
var_dump(in_array(1, $list));        // true (loose: 1 == '1')
var_dump(in_array(1, $list, true));  // false (strict: 1 !== '1')

// PHP 8.0 fix — 0 no longer equals non-numeric strings
$mixed = ['apple', 'banana'];
var_dump(in_array(0, $mixed));       // false in PHP 8+ (was true in PHP 7!)

// ---- array_search — returns key, not bool ----
$colors = ['red', 'green', 'blue', 'green'];
$key = array_search('green', $colors, true);
echo $key; // 1 (first occurrence)

// GOTCHA: key could be 0, which is falsy — must check with ===
if (false !== $key = array_search('red', $colors, true)) {
    echo "Found at key $key\n"; // Found at key 0
}

// ---- array_key_exists vs isset ----
$config = ['debug' => null, 'level' => 3];

var_dump(array_key_exists('debug', $config)); // true  — key exists, value null
var_dump(isset($config['debug']));            // false — null treated as absent

// ALSO: array_key_exists works on null arrays, isset throws notice on missing
var_dump(isset($config['nonexistent']));         // false (no notice in PHP 8)
var_dump(array_key_exists('nonexistent', $config)); // false

// ---- Performance pattern: flip large arrays for O(1) lookup ----
$validStatuses = ['active', 'pending', 'banned', 'suspended'];

// SLOW: in_array in a loop = O(n * m)
foreach ($users = ['alice', 'bob'] as $user) {
    if (in_array('active', $validStatuses, true)) { /* ... */ }
}

// FAST: flip once, then O(1) isset
$statusSet = array_flip($validStatuses);
// $statusSet = ['active' => 0, 'pending' => 1, 'banned' => 2, 'suspended' => 3]
foreach ($users as $user) {
    if (isset($statusSet['active'])) { /* O(1) */ }
}

// ---- Searching associative arrays by value ----
$map = ['alice' => 'admin', 'bob' => 'viewer'];
$name = array_search('admin', $map, true); // 'alice'

// ---- Null coalescing for safe access (not searching, but related) ----
$val = $config['missing'] ?? 'default'; // safe, no notice

Interview Q&A

Q: Why is in_array($value, $largeArray) an anti-pattern in performance-critical code, and what is the correct replacement?

in_array() performs a linear O(n) scan through the array values, comparing each element with $value. If you call it inside a loop of M iterations with an array of N elements, total complexity is O(N×M). The correct pattern for repeated membership testing is to convert the array to a hash set using array_flip() — which runs once in O(n) — and then use isset($flipped[$value]) for each lookup in O(1). The trade-off is one upfront O(n) allocation and the constraint that values must be valid array keys (strings or integers). For floating-point or object values, you cannot use this trick and must use a different data structure or accept the O(n) cost.


Q: array_key_exists() accepts a second argument that can be null in older code, while isset() does not. What is the exact difference in their null-value behavior and why does it matter for configuration systems?

isset($arr[$key]) returns false if the key does not exist OR if the key exists with a value of null. This is because isset() is designed to answer "is there a useful value here?" not "does this key exist?" array_key_exists('key', $arr) returns true if and only if the key exists in the HashTable, regardless of whether the value is null. In configuration systems, null often carries semantic meaning: "this setting is explicitly disabled" vs "this setting was never configured." Using isset() to check configuration keys collapses these two states into one, causing bugs where an explicitly-null config value is treated as missing. Always use array_key_exists() in config-layer code.


Q: array_search() returns false when not found, but a valid key could be 0. How do you write a correct check that works even when the key is 0, and what bug does the naive check produce?

The naive check if ($key = array_search($val, $arr)) fails when $key is 0, because 0 is falsy in PHP and the condition evaluates to false, incorrectly treating a successful match at index 0 as a "not found." The correct check uses strict identity comparison with false: if (false !== ($key = array_search($val, $arr, true))). Note the strict !== (three equals signs) — using != would still fail because 0 != false is false (loose comparison treats them as equal). This is one of the classic PHP operator gotchas: whenever a function can return either false or 0, you must use ===/!== to distinguish them.