In PHP 8.1 and later, what does array_is_list() check, and how does that relate to the engine's packed array layout?
answer
- keys exactly 0, 1, 2 in order
- empty array counts as a list
- added in PHP 8.1
- packed: bare values, no stored keys
- layout is invisible; semantics are not
basics
~20 sarray_is_list(), added in PHP 8.1, returns true when the keys are exactly 0, 1, 2 and so on in iteration order, with no gaps or string keys. Packed layout is an internal memory optimisation; array_is_list() tests the keys, not the layout.
solid answer
~50 s`array_is_list(array $array): bool` returns `true` when the array's keys are the consecutive integers `0` to `count - 1` **in iteration order**; an empty array counts as a list. A gap (`[0 => 'a', 2 => 'b']`), a string key, or the right keys in the wrong order (`[1 => 'b', 0 => 'a']`) all make it `false`. It exists because list-ness changes behaviour elsewhere, most visibly in JSON encoding, where a list becomes a JSON array and anything else an object. Internally, the engine can store an array whose integer keys grow in order in a **packed** layout, a plain vector of values with no stored keys or hash slots, which uses less memory. That layout is invisible to PHP code and not the same as being a list: a packed array can have holes. Code should ask `array_is_list()`, never assume from how the array was built.
code
php · 15 lines<?php
$ids = [101, 102, 103];
var_dump(array_is_list($ids)); // bool(true)
echo json_encode($ids), "\n"; // [101,102,103]
unset($ids[1]);
var_dump(array_is_list($ids)); // bool(false)
echo json_encode($ids), "\n"; // {"0":101,"2":103}
$ids = array_values($ids);
var_dump(array_is_list($ids)); // bool(true)
echo json_encode($ids), "\n"; // [101,103]
var_dump(array_is_list([1 => 'b', 0 => 'a'])); // bool(false): order counts
var_dump(array_is_list([])); // bool(true)go deeper
Recall that array_is_list() exists since PHP 8.1 and returns true only for keys 0, 1, 2 in order, including the empty array.
Explain which cases return false (gaps, string keys, wrong order) and why list-ness changes json_encode() output after unset() or filtering.
Separate the engine's packed layout from the list semantic, use array_values() and array_is_list() at boundaries, and keep large numeric arrays append-built.
Decide where list guarantees should be enforced in an API, by runtime checks, static types or typed collections, weighing cost against client breakage.
## What counts as a list PHP's array is always an ordered map, but much code treats some arrays as **lists**: sequences where the key is just the position. PHP 8.1 added `array_is_list(array $array): bool` to test that property precisely. It returns `true` when: 1. the array is empty, or 2. its first key is `0`, the next is `1`, and so on, with every key an `int` equal to its position in **iteration order**. | Array | `array_is_list()` | Why | |---|---|---| | `[]` | `true` | empty arrays are lists | | `['a', 'b', 'c']` | `true` | keys 0, 1, 2 in order | | `[0 => 'a', 2 => 'c']` | `false` | gap at 1 | | `[1 => 'b', 0 => 'a']` | `false` | right keys, wrong order | | `['a', 'x' => 'b']` | `false` | string key | | result of `unset($list[0])` | `false` | keys now start at 1 | | result of `array_values()` | `true` | renumbered from 0 | Before 8.1, code usually compared `array_keys($a) === range(0, count($a) - 1)`, which allocates two arrays and has an awkward empty-array edge case. `array_is_list()` answers the question without building anything. ## Why list-ness matters Several behaviours depend on whether an array is a list, which is why the check is worth having: - **JSON encoding.** `json_encode()` produces a JSON array for a list and a JSON object for anything else. Removing one element with `unset()` turns `["a","b","c"]` output into `{"0":"a","2":"c"}`, which breaks clients expecting an array. - **Validation at boundaries.** An API accepting "a list of IDs" can reject a map with `array_is_list()` before trusting positions. - **Destructuring and index loops** assume keys 0 to n-1; gaps produce *Undefined array key* warnings. ## The packed layout Internally, a PHP array is a hash table, but the engine has two storage layouts for it: | Layout | What each element stores | Used when | |---|---|---| | **packed** | only the value; the key is implied by the position | integer keys added in ascending order, the common list case | | **hash** | the value, the key, a cached hash, plus a slot in a separate index | string keys, or integer keys that do not fit the packed pattern | A packed array is essentially a vector of values. It skips the per-element key and hash bookkeeping, so it uses noticeably less memory per element and iterates quickly. The engine converts an array from packed to hash automatically when an operation needs it, such as inserting a string key, and PHP code never sees the difference: every function behaves the same on both. ## Packed is not the same as list It is tempting to equate the two, but the engine's own implementation of `array_is_list()` shows they differ: - A packed array can have **holes**. Unsetting an element in the middle may leave the array packed with an empty slot, and then `array_is_list()` is `false`. - A hash-layout array can still be a list, for example one that went through operations that forced the hash layout but ended up with keys 0 to n-1 in order. `array_is_list()` walks the keys and returns `true`. So the layout is an **optimisation** and `array_is_list()` is a **semantic** check. Performance advice follows from the layout (build lists by appending, avoid mixing string keys into large numeric arrays); correctness decisions should follow from `array_is_list()`. ## Practical guidance - Call `array_values()` after filtering or unsetting when the result must be a list, especially before `json_encode()`. - Use `array_is_list()` at input boundaries rather than trusting that an array was built as a list. - For very large numeric datasets, appending in order keeps the compact layout; interleaving string keys forfeits it.
- An endpoint that returned a JSON array suddenly returns a JSON object after a filtering change. What happened?The filter removed elements but kept the original integer keys, so the array had gaps and was no longer a list. `json_encode()` encodes non-lists as objects. Re-index with `array_values()` before encoding, and assert with `array_is_list()` in a test.
- Does array_is_list() tell you whether the engine stores the array in packed form?No. It checks the keys: 0 to n-1 in iteration order. A packed array with a hole is not a list, and a hash-layout array can still be one. The storage layout is an internal detail that PHP code cannot observe directly.
saying these in an interview costs you the question
- array_is_list() returns true for any array with only integer keys.
- An empty array is not considered a list.
- Keys 0 and 1 in reverse order still count as a list.
- A packed array is by definition a list.
- Code can call a function to switch an array between packed and hash layout.