PHP arrays, values, and comparison
Status: Complete. Last reviewed 2026-08-28.
A PHP array is one ordered map implementation serving two roles: list-like sequences and key/value maps. It is not a compact homogeneous vector by default. Keys, insertion order, comparison rules, copy-on-write, and references all affect representation, memory, and API behavior.
Keys define the shape
Section titled “Keys define the shape”Array keys are integers or strings. PHP converts strings containing valid decimal integers without a leading + to integer keys, so '8' and 8 address the same entry, while '08' remains a string key. Floats are converted to integers by truncation, booleans to 0 or 1, and null has historically become an empty-string key; relying on these conversions obscures input errors and PHP 8.5 deprecates null array offsets.
Insertion order is preserved. Removing an element does not reindex remaining integer keys, and appending chooses the next integer after the highest existing integer key under current rules. A list is specifically an array whose keys are consecutive integers from zero in order; array_is_list() tests that property. array_values() deliberately reindexes values when a list representation is required.
Array union ($left + $right) keeps all left-hand entries and adds only keys absent from the left. array_merge() overwrites later string keys and renumbers numeric keys. Spread/unpacking follows its documented key rules. These are different operations, not interchangeable ways to concatenate “arrays.” State the intended collision policy.
JSON exposes list-versus-map ambiguity
Section titled “JSON exposes list-versus-map ambiguity”json_encode() emits a JSON array only when the PHP array is a list. Gaps, a non-zero first key, changed order, or string keys make it a JSON object. A common failure is filtering a list: array_filter() preserves original keys, so removing an element leaves a gap and changes the JSON shape unless the result is reindexed.
Decoding JSON objects into associative arrays loses some distinction available in object form and introduces PHP key coercion. Large numeric identifiers may also exceed safe representation in downstream environments even when PHP can store them. Treat serialization as a schema boundary: define whether a field is a list or object, normalize deliberately, check encoding errors with exceptions, and contract-test actual payloads.
$values = ['first', 'second', 'third'];$filtered = array_filter($values, fn (string $v) => $v !== 'second');
json_encode($filtered, JSON_THROW_ON_ERROR); // object: keys 0 and 2json_encode(array_values($filtered), JSON_THROW_ON_ERROR); // array: keys 0 and 1Copy-on-write delays array copying
Section titled “Copy-on-write delays array copying”Array assignment has value semantics, but the engine can initially share underlying storage. When one logically independent array is mutated, PHP separates storage so the other value is unchanged. This copy-on-write behavior makes assignment cheap until a write requires separation; that write can suddenly allocate memory proportional to a large structure.
Passing a large array by value does not automatically copy all of its storage. Adding & as a supposed optimization changes semantics and can defeat assumptions rather than safely reducing memory. Profile peak memory at the mutation point. Avoid unnecessary large intermediate arrays, consider generators or domain-specific structures, and remember that PHP arrays carry hash-table overhead per entry.
Nested values follow their own semantics. Nested arrays separate when mutated through one owner, while nested objects are object handles and remain shared unless cloned. “The array copied” therefore does not mean every reachable object became independent.
The copy-on-write probe demonstrates the memory step and value separation. Exact bytes depend on the build; the behavioral relationship is the lesson.
References alias storage
Section titled “References alias storage”References explicitly make variable names or array elements alias the same variable container. They are not pointers that can be arithmetically manipulated, and they are separate from object-handle sharing and copy-on-write.
The classic failure is a by-reference foreach:
foreach ($rows as &$row) { $row['active'] = true;}unset($row);After the loop, $row remains an alias to the final element unless unset. A later ordinary foreach ($rows as $row) can repeatedly overwrite that final element. Prefer mapping to a returned result when practical; if in-place mutation is intended, unset the reference immediately and keep its scope small.
References embedded in arrays can also survive copies in surprising ways because the copied array value may contain an aliased element container. Avoid reference-heavy structures at application boundaries and use focused probes when maintaining legacy code.
Comparison must match the boundary
Section titled “Comparison must match the boundary”=== requires the same type and value; == performs type juggling. For arrays, strict comparison requires the same key/value pairs in the same order with strict value equality. Loose array equality allows compatible key/value pairs regardless of order and compares values loosely. Relational comparison between arrays follows PHP-specific rules and should not define business ordering.
Loose comparison has changed across major PHP versions and remains risky for identifiers, authentication data, signatures, zero-like values, and sentinel returns. Use hash_equals() for timing-safe secret comparison where appropriate, explicit parsing for numeric input, and strict comparison after normalization. Do not apply === blindly before deciding whether two differently represented inputs should normalize to the same domain value.
in_array() and array_search() are loose unless their strict flag is enabled. A search result can be key 0, so compare it with false strictly. The same rule applies to APIs returning a valid zero-like result or false on failure.
Missing, null, empty, and false are distinct
Section titled “Missing, null, empty, and false are distinct”isset($array['key']) is false both for a missing key and a present key containing null. array_key_exists() distinguishes presence from value. The null-coalescing operator behaves like an isset check and therefore falls back for both missing and null.
empty() folds missing values, null, false, numeric zero, zero-like strings such as '0', and empty containers into one branch. That is convenient only when the domain genuinely treats them alike. At request and configuration boundaries, explicit presence and type checks usually produce better errors and preserve intent.
Production decision guide
Section titled “Production decision guide”- Use a PHP list only when consecutive order is part of the contract; normalize after key-preserving filters.
- Use a keyed array for small local maps, but introduce a DTO/value object when fields form a durable schema.
- Use strict lookup/comparison after parsing identifiers and boundary scalars.
- Avoid copying/mutating huge arrays without measuring peak memory.
- Avoid references unless aliasing is the explicit API contract.
- Choose SPL or extension-backed structures only after profiling; lower memory can trade away ergonomics or interoperability.
Current and legacy context
Section titled “Current and legacy context”- Current: PHP 8.5 provides
array_is_list(),array_first(), andarray_last()and tightens diagnostics around questionable conversions. JSON exception mode and strict comparison flags should be routine at boundaries. - Common: PHP 8.2–8.4 applications rely heavily on arrays as both records and collections; static-analysis shapes can improve internal precision during migration.
- Legacy: Loose comparison, reference loops, sentinel
false, and ad hoc associative DTOs require characterization tests because “cleanup” can change observable coercion and key behavior.
Interview practice
Section titled “Interview practice”- PHP-ARRAYS-01 — Predict array key normalization
- PHP-ARRAYS-02 — Diagnose a JSON shape change
- PHP-ARRAYS-03 — Explain copy-on-write and peak memory
- PHP-ARRAYS-04 — Diagnose reference leakage
- PHP-ARRAYS-05 — Choose strict comparison and normalization
- PHP-ARRAYS-06 — Distinguish missing, null, false, and empty