array_is_list, and twenty years of guessing

Deciding whether an array was a list or a map had been done with array_keys($a) === range(0, count($a) - 1), which allocates two arrays to answer a question the engine already knows.

array_is_list([]);              // true
array_is_list([1, 2, 3]);       // true
array_is_list([0 => 'a', 1 => 'b']);   // true

array_is_list([1 => 'a']);      // false
array_is_list(['a' => 1]);      // false

$a = [0 => 'a', 1 => 'b'];
unset($a[0]);
array_is_list($a);              // false — the gap matters

The unset case is the whole reason this is worth having: an array that was a list and had an element removed is no longer one, and code that serialises it to JSON silently produces an object rather than an array. That is a bug that reaches an API client rather than a test. The function is O(1) in the common case because the engine tracks packed arrays internally, which no userland implementation could match.