skip to content

In PHP, what does iterator_to_array() do with keys by default, and when does it silently lose elements?

level: middleimportance: should knowfreq 28%

answer

  1. keys are kept unless told otherwise
  2. $preserve_keys defaults to true
  3. duplicate key: last value wins
  4. AppendIterator restarts keys at 0
  5. accepts plain arrays since 8.2

basics

~20 s

iterator_to_array() copies every element into an array and, because $preserve_keys defaults to true, uses the iterator's keys. When two elements share a key the later one overwrites the earlier; pass false to get a list of all values.

solid answer

~40 s

`iterator_to_array(iterable $iterator, bool $preserve_keys = true): array` runs the iterator to the end and stores each `current()` under its `key()`. With the default `true`, repeated keys collide and only the last value survives, with no warning. The classic case is an `AppendIterator` over two `ArrayIterator`s: both restart at key `0`, so the result holds only the second list. Passing `false` ignores keys and returns a list of every value in order. Two more things matter: it **drives** the iterator, so a structure whose iteration removes elements, such as `SplPriorityQueue`, is empty afterwards; and it **materialises** everything at once, trading a lazy, constant-memory loop for a full array in memory. Since PHP 8.2 it also accepts a plain array.

code

php · 10 lines
php
<?php
declare(strict_types=1);

$all = new AppendIterator();
$all->append(new ArrayIterator(['low', 'normal']));
$all->append(new ArrayIterator(['high', 'urgent']));

var_dump(iterator_count($all));           // int(4)
var_dump(iterator_to_array($all));        // [0 => 'high', 1 => 'urgent']
var_dump(iterator_to_array($all, false)); // ['low', 'normal', 'high', 'urgent']

go deeper

for a junior

Remember that the second argument, $preserve_keys, defaults to true, and that passing false gives a plain list of every value.

for a middle

Explain how repeated keys overwrite earlier values, name AppendIterator as the classic source, and note that the call drives the iterator to its end.

for a senior

Spot conversions that defeat a lazy iterator's memory profile or empty a destructive SPL structure, and keep large sources in a foreach instead.

for a principal

Decide where a codebase returns arrays versus iterables, weighing caller convenience against memory and the hidden cost of materialising streams.

## The signature and the default In PHP 8.5 the function is declared as: ```php function iterator_to_array(iterable $iterator, bool $preserve_keys = true): array ``` It rewinds the iterator, then for each element reads `current()` and, when `$preserve_keys` is `true`, `key()`, and writes `$result[$key] = $value`. That default is the source of most surprises: an **array cannot hold two entries with the same key**, so a later element with a repeated key simply overwrites the earlier one. There is no warning and no error; the result is just shorter than the number of elements the iterator produced. With `$preserve_keys = false`, keys are ignored and every value is appended, so the result is a list numbered from `0` containing all elements in iteration order. ## Where duplicate keys come from Iterators are not arrays, so nothing stops them from yielding the same key twice: - **`AppendIterator`** chains several iterators; each inner `ArrayIterator` over a list starts again at key `0`. - A **hand-written `Iterator`** whose `key()` returns something repeating, such as a category name or a date. - **Iterators that walk nested data**, where different branches reuse the same local keys. In every one of these cases the default call quietly keeps only the last value per key. `iterator_count()` on the same iterator reports the full number, which is a quick way to spot the discrepancy. ## Key conversion rules When keys are preserved they are converted as array keys always are: | Key produced by the iterator | Stored as | |---|---| | `int`, `string` | unchanged | | `float` | truncated to `int` | | `bool` | `0` or `1` | | `null` | `""` (empty string) | | `array` or `object` | a warning is raised | This is another path to collisions: `1.2` and `1.7` both become `1`. ## It drives and it materialises 1. **It consumes the iteration.** The function calls `rewind()` and then walks to the end. For most iterators that is harmless, but some SPL structures remove elements as they are iterated: `SplPriorityQueue` and the heaps extract on each step, and an `SplQueue` switched to delete mode dequeues. After `iterator_to_array()` such a structure is empty. 2. **It builds the whole array.** A `foreach` over an iterator holds one element at a time; `iterator_to_array()` holds all of them. For an iterator over a large file, a database cursor or a long computed sequence, converting to an array throws away the reason the iterator existed and can exhaust `memory_limit`. 3. **It needs a rewindable source.** An iterator that cannot restart can be converted once; a second call either returns nothing or throws, depending on the class. The rule of thumb: loop over the iterator when you only need each element once, and convert to an array only when you truly need random access, sorting with the `sort` family, or a value to return from an API that promises an array. ## Version notes - **PHP 8.2** widened the first parameter to `iterable`, so `iterator_to_array($maybeArray)` works whether the caller passed an array or a `Traversable`. With an array and `$preserve_keys = false` it behaves like `array_values()`. - `iterator_count()` got the same widening and also drives the iterator to the end. ## A hand-written iterator with repeating keys Duplicate keys are easy to create by accident in your own classes. Suppose an `Iterator` over support tickets returns the ticket's **category** from `key()`, so that `foreach ($tickets as $category => $ticket)` reads naturally. Every category appears many times, and `iterator_to_array($tickets)` returns one ticket per category — the last one seen — with no hint that the rest were dropped. The loop was correct; only the conversion loses data. Either convert with `false` and keep the category inside each value, or group explicitly: ```php $byCategory = []; foreach ($tickets as $category => $ticket) { $byCategory[$category][] = $ticket; } ``` This is also the honest way to express intent: grouping is a decision, while `iterator_to_array()` with preserved keys silently decides "last one wins" for you. ## Checklist when converting - Do the keys mean something? Keep the default only if every key is unique. - Are the keys just positions? Pass `false`. - Is the source destructive or huge? Iterate it with `foreach` instead.

  • Why is iterator_to_array() on an SplPriorityQueue a trap?
    Iterating an `SplPriorityQueue` extracts elements: each `next()` removes the top. `iterator_to_array()` walks it to the end, so you get the elements in priority order but the queue is empty afterwards. Iterate a `clone` of the queue if the original must keep its contents.
  • When is converting an iterator to an array the wrong move?
    When the iterator exists to keep memory flat, such as a large file read line by line or a lazily computed sequence. Converting holds every element at once and can hit `memory_limit`; a `foreach` over the iterator processes one element at a time.

saying these in an interview costs you the question

  • iterator_to_array() always returns a list numbered from 0
  • Duplicate iterator keys raise a warning or an exception
  • Converting an iterator leaves a destructive SPL structure untouched
  • Converting to an array is free because iterators are lazy anyway
  • iterator_to_array() rejects a plain array in PHP 8.5