Arrays7 min readExamples run on PHP 8.5.10

range()

Learn how PHP 8.4 range() builds arrays of integers, floats or characters, how step works, and which inputs raise errors.

Signature

range(string|int|float $start, string|int|float $end, int|float $step = 1): array

Purpose

range() builds an array from a starting value to an ending value. You give it the two boundaries and, optionally, the distance between neighbouring elements. It is a compact way to produce page numbers, index lists, countdowns, alphabet slices or evenly spaced floats without writing a loop.

Signature

Parameters and defaults

  • $start is the first value of the sequence.
  • $end is the last possible value. The sequence stops at $end or at the last element before it, depending on $step.
  • $step is the distance between values. It defaults to 1. A float step with no fractional part is treated as an integer.

The direction comes from the boundaries, not from the sign of the step. If $start is less than or equal to $end, the sequence increases. Otherwise it decreases. A positive step is therefore fine for a decreasing range, and range(5, 1, 2) counts down.

Return value

The function always returns a list-style array. The element type depends on the input:

  • If both boundaries are strings and the step is an integer, the result is a sequence of bytes, generally ASCII characters.
  • If at least one of $start, $end or $step is a float, the result is a sequence of floats.
  • Otherwise the result is a sequence of integers.

Failure cases

PHP 8.4 throws a ValueError when:

  • $step is 0.
  • $start, $end or $step is not finite, such as INF or NAN.
  • $step is negative but the range is increasing ($start <= $end).

A TypeError is thrown if $start or $end is an array, object or resource. An E_WARNING is emitted if a boundary is an empty string (treated as 0), a non-numeric string longer than one byte, or a string that must be cast to an integer because the other boundary is a number.

Example 1: integers and steps

The default step is 1. A larger step skips values, and a decreasing range works with a positive step.

Example
<?php
echo json_encode(range(1, 5)), PHP_EOL;
echo json_encode(range(0, 100, 25)), PHP_EOL;
echo json_encode(range(5, 1, 2)), PHP_EOL;
Output
[1,2,3,4,5]
[0,25,50,75,100]
[5,3,1]

The last call produces 5, 3, 1. The value 1 is reached exactly, so it is included.

Example 2: characters and floats

When both boundaries are strings, range() walks through byte values. With a float step you get float values.

Example
<?php
echo json_encode(range('a', 'e')), PHP_EOL;
echo json_encode(range('e', 'a', 2)), PHP_EOL;
echo implode(',', range(0, 1, 0.25)), PHP_EOL;
Output
["a","b","c","d","e"]
["e","c","a"]
0,0.25,0.5,0.75,1

The character example uses the ASCII order of the bytes. This is why a range such as 'A' to 'z' also includes punctuation characters that sit between uppercase and lowercase letters.

Example 3: handling invalid steps

Because a bad step throws, wrap dynamic input in a small helper if it comes from outside your code.

Example
<?php
function tryRange(int|float|string $start, int|float|string $end, int|float $step): string
{
    try {
        return json_encode(range($start, $end, $step));
    } catch (ValueError $e) {
        return 'ValueError';
    }
}

echo tryRange(1, 3, 0), PHP_EOL;
echo tryRange(1, 3, -1), PHP_EOL;
echo tryRange(3, 1, -1), PHP_EOL;
Output
ValueError
ValueError
[3,2,1]

A zero step and a negative step on an increasing range both fail. A negative step on a decreasing range is valid.

Edge cases

  • Equal boundaries: if $start equals $end, the result contains that single value.
  • Step larger than the span: the array contains only $start, because the next value would overshoot $end.
  • Steps that do not land on the end: range(0, 10, 4) ends at 8, not 10.
  • Float precision: decimal fractions such as 0.1 cannot be represented exactly in binary. Round the values or compare with a tolerance before testing them for equality.
  • Keys: the keys are always 0, 1, 2, …, regardless of the values.
  • Size: the whole array is built in memory. For a very large span, a for loop or a generator is a better fit.

Common mistakes

  1. Using a negative step for an increasing range. In PHP 8.3 and later this throws a ValueError. Use a positive step, or swap the boundaries.
  2. Passing 0 as the step. Validate user-supplied steps before calling the function.
  3. Mixing numbers and strings. A call like range('a', 5) forces one boundary to be cast and produces a warning. Pass values of the type you intend.
  4. Passing multi-character strings. Non-numeric strings longer than one byte trigger a warning, so range('aa', 'zz') does not produce two-letter combinations.
  5. Expecting inclusive ends every time. The end is only included when the step lands exactly on it.
  6. Assuming range() is lazy. It allocates every element at once.
  • array_fill() creates an array of repeated values for a given index range.
  • shuffle() randomizes the order of an existing array, which is useful after generating a range.
  • foreach iterates over the produced array.
  • array_map() and array_filter() can transform or trim a generated range.

Recap

range() creates an array from $start to $end using an optional $step. The direction depends on the boundaries, the element type depends on the arguments, and it throws a ValueError for a zero, non-finite or wrongly signed step. Keep the argument types consistent, remember that the end value is not guaranteed to appear, and avoid it for extremely large spans.

Tested examples

Integer ranges with default, larger and decreasing steps
<?php
echo json_encode(range(1, 5)), PHP_EOL;
echo json_encode(range(0, 100, 25)), PHP_EOL;
echo json_encode(range(5, 1, 2)), PHP_EOL;
Output
[1,2,3,4,5]
[0,25,50,75,100]
[5,3,1]
Character ranges and a float step
<?php
echo json_encode(range('a', 'e')), PHP_EOL;
echo json_encode(range('e', 'a', 2)), PHP_EOL;
echo implode(',', range(0, 1, 0.25)), PHP_EOL;
Output
["a","b","c","d","e"]
["e","c","a"]
0,0.25,0.5,0.75,1
Catching ValueError for invalid steps
<?php
function tryRange(int|float|string $start, int|float|string $end, int|float $step): string
{
    try {
        return json_encode(range($start, $end, $step));
    } catch (ValueError $e) {
        return 'ValueError';
    }
}

echo tryRange(1, 3, 0), PHP_EOL;
echo tryRange(1, 3, -1), PHP_EOL;
echo tryRange(3, 1, -1), PHP_EOL;
Output
ValueError
ValueError
[3,2,1]