Types7 min readExamples run on PHP 8.5.10

is_iterable()

Learn how PHP 8.4 is_iterable() tells arrays and Traversable objects from other values, with examples, edge cases and common mistakes.

Signature

is_iterable(mixed $value): bool
Available since PHP 7.1.07.48.08.18.28.38.48.5All versions →

Purpose

is_iterable() answers one question: could this value be accepted by the iterable pseudo-type? In practice that means the value is either an array or an object that implements Traversable. It is a runtime check you can use before looping over input whose type you do not control, such as a function argument declared mixed, a decoded configuration value, or data handed over by a plugin.

Signature

Parameters and defaults

  • $value: any value. There are no other parameters and no defaults. Because the type is mixed, you can pass integers, strings, null, arrays, objects or anything else without causing a type error.

Return value and failure cases

The function returns true when the value is an array or a Traversable object, and false for everything else. It never throws for ordinary values and does not emit warnings. The result is a plain boolean, so it works directly inside if conditions.

The check does not look inside the value. An empty array is iterable. A generator is iterable. The function does not advance, rewind or consume anything, so calling it on a generator leaves the generator untouched.

Example 1: Scalars, null, arrays and plain objects

This example runs several kinds of value through the function and prints the results as JSON.

Example
<?php
$values = [
    'array' => [1, 2, 3],
    'empty array' => [],
    'string' => 'abc',
    'int' => 1,
    'null' => null,
    'stdClass' => new stdClass(),
];
$result = [];
foreach ($values as $label => $value) {
    $result[$label] = is_iterable($value);
}
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"array":true,"empty array":true,"string":false,"int":false,"null":false,"stdClass":false}

Notice that a string is not iterable even though you can read its characters by offset, and that a plain stdClass object is not iterable even though foreach can walk its public properties.

Example 2: Traversable objects

Any class that implements Iterator or IteratorAggregate is Traversable, and so is every generator. A class that implements only some other interface, such as Countable, does not qualify.

Example
<?php
function makeGenerator(): Generator {
    yield 1;
    yield 2;
}
class Box implements Countable {
    public function count(): int { return 0; }
}
$result = [
    'ArrayIterator' => is_iterable(new ArrayIterator([1, 2])),
    'ArrayObject' => is_iterable(new ArrayObject([1, 2])),
    'Generator' => is_iterable(makeGenerator()),
    'Box' => is_iterable(new Box()),
];
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"ArrayIterator":true,"ArrayObject":true,"Generator":true,"Box":false}

Example 3: Guarding a loop

A common use is validating input at the top of a function that accepts several shapes of collection.

Example
<?php
function sumItems(mixed $items): int {
    if (!is_iterable($items)) {
        throw new InvalidArgumentException('Expected an iterable value.');
    }
    $total = 0;
    foreach ($items as $item) {
        $total += $item;
    }
    return $total;
}
function numbers(): Generator {
    yield 4;
    yield 5;
}
echo sumItems([1, 2, 3]), "\n";
echo sumItems(numbers()), "\n";
try {
    sumItems(10);
} catch (InvalidArgumentException $e) {
    echo $e->getMessage(), "\n";
}
Output
6
9
Expected an iterable value.

If the parameter could simply be declared iterable, PHP would reject bad values for you with a TypeError. The explicit check is useful when you want a custom exception, a fallback, or when the parameter has to stay mixed.

Edge cases

  • Empty arrays are iterable. The check is about type, not content.
  • Generators are Traversable, so they pass. Iterating one consumes it, but is_iterable() itself does not.
  • Objects with public properties are not iterable unless their class implements Traversable, even though foreach accepts them.
  • Strings are never iterable. Use str_split() or index access if you need characters.
  • null returns false, so an optional collection argument needs a separate null check or a default.

Common mistakes

  1. Assuming is_iterable() means countable. A generator is iterable but cannot be counted with count(). Use is_countable() for that question.
  2. Using is_array() when objects should also be accepted. is_array() rejects ArrayIterator, ArrayObject and generators.
  3. Treating a true result as permission to index the value with $items[0]. Only arrays and ArrayAccess objects support that.
  4. Looping twice over a value that passed the check. A generator cannot be rewound after it has advanced, so the second pass fails.
  5. Expecting plain objects to pass because foreach can read their properties.
  • is_array() checks only for arrays.
  • is_countable() checks for arrays or Countable objects.
  • is_object() checks for any object.
  • iterator_to_array() converts a Traversable into an array, and works only after you know you have a Traversable.

Recap

is_iterable() takes one mixed value and returns true for arrays and Traversable objects, false otherwise. It is safe to call on any value, does not consume generators, and is the right guard when a function must accept both arrays and iterator-like objects.

Tested examples

Checking scalars, null, arrays and plain objects
<?php
$values = [
    'array' => [1, 2, 3],
    'empty array' => [],
    'string' => 'abc',
    'int' => 1,
    'null' => null,
    'stdClass' => new stdClass(),
];
$result = [];
foreach ($values as $label => $value) {
    $result[$label] = is_iterable($value);
}
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"array":true,"empty array":true,"string":false,"int":false,"null":false,"stdClass":false}
Traversable objects versus other objects
<?php
function makeGenerator(): Generator {
    yield 1;
    yield 2;
}
class Box implements Countable {
    public function count(): int { return 0; }
}
$result = [
    'ArrayIterator' => is_iterable(new ArrayIterator([1, 2])),
    'ArrayObject' => is_iterable(new ArrayObject([1, 2])),
    'Generator' => is_iterable(makeGenerator()),
    'Box' => is_iterable(new Box()),
];
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"ArrayIterator":true,"ArrayObject":true,"Generator":true,"Box":false}
Guarding a loop with is_iterable()
<?php
function sumItems(mixed $items): int {
    if (!is_iterable($items)) {
        throw new InvalidArgumentException('Expected an iterable value.');
    }
    $total = 0;
    foreach ($items as $item) {
        $total += $item;
    }
    return $total;
}
function numbers(): Generator {
    yield 4;
    yield 5;
}
echo sumItems([1, 2, 3]), "\n";
echo sumItems(numbers()), "\n";
try {
    sumItems(10);
} catch (InvalidArgumentException $e) {
    echo $e->getMessage(), "\n";
}
Output
6
9
Expected an iterable value.