skip to content

In PHP, which key does $arr[] = $value assign, and why does it skip numbers after elements are unset?

level: middleimportance: should knowfreq 38%

answer

  1. largest int key ever used, plus one
  2. unset() does not lower the counter
  3. array_values() re-indexes
  4. negative first key: n+1 since 8.3
  5. PHP_INT_MAX key blocks appending

basics

~20 s

$arr[] = $value uses one more than the largest integer key the array has held since it was last re-indexed, or 0 if it never had one. unset() does not lower that counter, so appended keys can skip numbers until array_values() re-indexes.

solid answer

~50 s

Each array tracks a **next free integer key**. Appending with `$arr[] = $value` (or `array_push()`, or listing a value without a key in a literal) uses that key, and any integer key written that is at least as large moves it to key + 1. String keys never affect it. The counter remembers the largest key the array has **ever** held: after `unset($arr[4])` on `[0..4]`, the next append still gets key 5. `array_values()` builds a new array numbered from 0, which resets it. Since PHP 8.3, an array whose first integer key is negative continues from that key: `[-5 => 'a']` then `[] =` gives key -4, where older versions gave 0. If the key `PHP_INT_MAX` has been used, appending throws `Error`: *Cannot add element to the array as the next element is already occupied*.

code

php · 16 lines
php
<?php
$a = ['a', 'b', 6 => 'c', 'd'];   // keys 0, 1, 6, 7
unset($a[7]);
$a[] = 'e';                       // key 8, not 7

$b = [1, 2, 3];
array_pop($b);                    // removes key 2 and lowers the counter
$b[] = 9;                         // key 2 again

$c = array_values($a);            // keys 0, 1, 2, 3
$c[] = 'f';                       // key 4

$d = [];
$d[-5] = 'x';
$d[] = 'y';                       // key -4 since PHP 8.3 (0 before)
print_r([$a, $b, $c, $d]);

go deeper

for a junior

Recall that [] appends with one more than the largest integer key and that unset() leaves a gap rather than renumbering.

for a middle

Explain the counter's rules, why unset() never lowers it, how array_values(), array_pop() and array_shift() differ, and the PHP 8.3 negative-key change.

for a senior

Catch index-based loops and list assumptions broken by gaps, and know which operations renumber so appended keys stay predictable after edits.

for a principal

Decide where code should rely on keys as stable identities and where it should renumber into plain lists, and make that choice explicit in APIs.

## The counter behind [] Every PHP array carries one piece of bookkeeping for appends: the **next free integer key**. It is not visible as a function, but it decides every key PHP picks for you: - `$arr[] = $value;` - `array_push($arr, $value);` - a value without a key inside an array literal, as in `['a', 'b', 6 => 'c', 'd']`. The manual phrases the rule as: if no key is given, PHP uses the increment of the largest previously used `int` key; if there has never been one, the key is 0. ## How the counter moves 1. A new empty array has no integer keys yet, so the first append gets key 0. 2. Writing an integer key that is greater than or equal to the counter moves it to that key + 1. After `$a[6] = 'c';`, the next append gets 7. 3. Writing a smaller integer key, such as `$a[2]` when the counter is 7, leaves it alone. 4. String keys, including non-canonical ones like `'08'`, never touch it. 5. `unset()` removes an element but **does not** lower the counter, even if you remove the element with the largest key. | Code | Keys afterwards | Next append | |---|---|---| | `$a = ['a', 'b'];` | 0, 1 | 2 | | `$a[6] = 'c';` | 0, 1, 6 | 7 | | `$a['x'] = 'd';` | 0, 1, 6, 'x' | 7 | | `unset($a[6]);` | 0, 1, 'x' | still 7 | | `$a = array_values($a);` | 0, 1, 2 | 3 | The manual's example makes the `unset()` point directly: remove every element from `[1, 2, 3, 4, 5]`, append once, and the new element gets key 5, not 0. ## Why unset leaves gaps This is a deliberate property of an ordered map, not a leak. Keys are identities: code may hold key 4 somewhere and expect it to mean the element that was at 4. Reusing it after a removal would silently point that code at a different element. So after `unset()` the array simply has a gap, and PHP never shifts or renumbers keys unless you ask. Consequences worth knowing: - A `for ($i = 0; $i < count($a); $i++)` loop over an array with gaps reads missing keys and emits *Undefined array key* warnings; `foreach` does not have this problem. - An array with gaps is no longer a list, which changes how some consumers treat it. - Functions that return a new array may or may not keep keys; `array_values()` is the explicit way to renumber. ## Functions that do move the counter back A few functions manage the counter themselves: - `array_values($a)` returns a fresh array numbered 0 to n-1. - `array_pop($a)` removes the last element and, when that element held the highest integer key, lowers the counter by one, so the next append reuses that key. - `array_shift($a)` removes the first element and renumbers the remaining integer keys from 0. ## Negative keys and the PHP 8.3 change Before PHP 8.3, a negative first key did not start the sequence: `$a = []; $a[-5] = 'x'; $a[] = 'y';` put `'y'` at key 0. Since 8.3, the counter follows the negative key, so `'y'` lands at -4. Code that relied on the old jump to 0 behaves differently after an upgrade. ## The ceiling The counter cannot move past `PHP_INT_MAX`. Once an array holds that key, any append throws `Error` with *Cannot add element to the array as the next element is already occupied*. It is rare, but it shows up when an integer key comes from untrusted input or a hash value.

  • After unset() created gaps, how do you get a clean list numbered from 0 again?
    Assign the result of `array_values($array)`, which returns a new array with the same values in the same order and keys 0 to n-1. Its next-key counter starts after the last of those keys, so appends continue the sequence without gaps.
  • Does removing the last element with array_pop() behave like unset() for the next append?
    No. When the popped element held the highest integer key, `array_pop()` lowers the next-key counter by one, so the following `[] =` reuses that key. `unset()` never lowers the counter, so the following append skips the removed key.

saying these in an interview costs you the question

  • $arr[] = $v uses count($arr) as the new key.
  • unset() on the last element lets the next append reuse its key.
  • String keys advance the automatic integer counter.
  • After unset(), PHP shifts the remaining keys down to close the gap.
  • A negative first key still makes the next appended key 0 in PHP 8.5.