PHP Indexed Arrays
An indexed array in PHP is simply an array whose keys are sequential integers, starting at 0 by default. It’s the array style most people learn first — a numbered list of values you can loop through, sort, and modify. Indexed arrays are the backbone of everyday PHP tasks like storing a list of names, prices, or form results, and understanding how PHP assigns and tracks those numeric keys will save you from some of the most common bugs beginners run into.
Overview: How Indexed Arrays Work
In PHP, there is really only one array type under the hood: an ordered hash map (internally the Zend engine’s HashTable structure). What we call an “indexed array” is just an array where every key happens to be a sequential integer. There’s no separate “list” or “vector” type in PHP the way some languages have — indexed arrays and associative arrays are the same data structure, distinguished only by what kind of keys you use.
When you create an array without specifying keys, PHP automatically assigns integer keys starting at 0 and incrementing by 1 for each new element:
<?php
$fruits = ["apple", "banana", "cherry"];
// Internally: [0 => "apple", 1 => "banana", 2 => "cherry"]
PHP’s engine keeps an internal pointer to the “next free index” for each array. Every time you push a value with $array[] = $value, PHP uses that pointer, stores the value, and increments the pointer. This is why indexed arrays stay sequential as long as you only ever append — but as you’ll see in the Common Mistakes section, removing elements can break that sequence.
Indexed arrays also preserve insertion order, not numeric or alphabetical order. If you build an array by assigning out-of-order keys, PHP will iterate the elements in the order they were inserted, not the order of their key values, unless you explicitly sort the array.
Syntax
There are a few equivalent ways to declare and build an indexed array:
| Form | Description |
|---|---|
$arr = [val1, val2, val3]; |
Short array syntax (recommended since PHP 5.4). Keys default to 0, 1, 2… |
$arr = array(val1, val2, val3); |
The older, long-form constructor. Functionally identical to []. |
$arr[] = value; |
Appends a value at the next available integer index. |
$arr[5] = value; |
Explicitly assigns a value at index 5, which also moves the “next free index” pointer to 6. |
$arr = []; |
Creates an empty array, ready to be appended to. |
Here’s all of that in one script:
<?php
$a = array(1, 2, 3);
$b = [1, 2, 3];
$c = [];
$c[] = "x";
$c[] = "y";
var_dump($a === $b);
Output:
bool(true)
array() and [] produce identical arrays — [] is just shorter and is the modern convention.
Examples
Example 1: Creating and Accessing an Indexed Array
<?php
$fruits = ["apple", "banana", "cherry"];
echo $fruits[0] . "\n";
echo $fruits[1] . "\n";
echo $fruits[2] . "\n";
echo "Total fruits: " . count($fruits) . "\n";
Output:
apple
banana
cherry
Total fruits: 3
Each element is accessed with square-bracket notation using its zero-based position. count() tells you how many elements exist, which is essential for loops and validation.
Example 2: Looping and Appending
<?php
$scores = [88, 92, 75, 60, 99];
foreach ($scores as $index => $score) {
$status = $score >= 70 ? "pass" : "fail";
echo "Student {$index}: {$score} ({$status})\n";
}
$scores[] = 100;
echo "New student count: " . count($scores) . "\n";
echo "Last score: " . $scores[count($scores) - 1] . "\n";
Output:
Student 0: 88 (pass)
Student 1: 92 (pass)
Student 2: 75 (pass)
Student 3: 60 (fail)
Student 4: 99 (pass)
New student count: 6
Last score: 100
foreach ($array as $index => $value) gives you both the position and the value on each pass. Appending with $scores[] = 100 automatically lands at index 5, since PHP tracks the next free index for you.
Example 3: Sorting a Real-World List
<?php
$prices = [19.99, 5.49, 42.00, 8.75, 15.30];
sort($prices);
echo "Sorted prices:\n";
foreach ($prices as $i => $price) {
printf("%d: $%.2f\n", $i, $price);
}
$cheapest = $prices[0];
$mostExpensive = $prices[count($prices) - 1];
echo "Cheapest: $" . number_format($cheapest, 2) . "\n";
echo "Most expensive: $" . number_format($mostExpensive, 2) . "\n";
Output:
Sorted prices:
0: $5.49
1: $8.75
2: $15.30
3: $19.99
4: $42.00
Cheapest: $5.49
Most expensive: $42.00
sort() re-orders the values and re-indexes the keys from 0 upward, which is exactly what you want for indexed arrays. Once sorted, the first and last elements are reliably the minimum and maximum.
How It Works Step by Step (Under the Hood)
When PHP executes $fruits = ["apple", "banana", "cherry"];, several things happen inside the Zend engine:
- A new
HashTableis allocated to back the array. - For each literal value, PHP inserts a bucket keyed by the next integer (0, then 1, then 2), storing both the key and a pointer to the value.
- An internal counter, often referred to as the “next free element,” is updated to 3 after the three inserts.
- The HashTable also maintains a doubly linked list of buckets in insertion order, which is what makes
foreachiterate in the order you added elements rather than in the order the keys are stored internally for lookups.
When you later write $fruits[] = "date";, PHP doesn’t scan the array to find the highest key — it simply reads the “next free element” counter (which is 3), inserts at that key, and bumps the counter to 4. This is O(1), not O(n), which is why appending is fast even on large arrays.
If you assign to an explicit key higher than the current counter, e.g. $fruits[10] = "fig";, PHP updates the “next free element” counter to 11 — so the next [] = append will land at 11, not 4. This is the root cause of many “why did my array skip indexes” surprises.
Because PHP arrays are always ordered maps rather than contiguous memory blocks like a C array, random access by key ($fruits[2]) is a fast hash lookup, but there is no guarantee that the “3rd element by position” and “the element at key 2” are the same thing once keys have gaps — which brings us to the most common mistake.
Common Mistakes
Mistake 1: Assuming the last valid index equals count()
Beginners often assume the last index of an array is the same number as its count, forgetting that indexing starts at 0.
<?php
$colors = ["red", "green", "blue"];
$lastIndex = count($colors);
var_dump(array_key_exists($lastIndex, $colors));
Output:
bool(false)
With 3 elements, valid indexes are 0, 1, and 2 — but count($colors) is 3, which doesn’t exist as a key. Accessing $colors[3] directly would trigger an “Undefined array key” warning and return null.
Corrected:
<?php
$colors = ["red", "green", "blue"];
$lastIndex = count($colors) - 1;
echo $colors[$lastIndex];
Output:
blue
Mistake 2: Looping by index after removing an element
Removing an element with unset() leaves a gap in the keys — the array is no longer perfectly sequential, even though count() still reports a smaller, seemingly “safe” number.
<?php
$numbers = [10, 20, 30, 40];
unset($numbers[1]);
for ($i = 0; $i < count($numbers); $i++) {
echo $numbers[$i] . "\n";
}
After removing index 1, the array is [0 => 10, 2 => 30, 3 => 40] with a count of 3. The loop only runs for $i = 0, 1, 2 — it prints 10, then hits the missing key 1 (triggering an “Undefined array key” warning and printing an empty line), then prints the value at key 2, which is 30. The value 40, sitting at key 3, is never reached at all.
Corrected — re-index with array_values(), or simply use foreach, which doesn’t care about key gaps:
<?php
$numbers = [10, 20, 30, 40];
unset($numbers[1]);
$numbers = array_values($numbers);
foreach ($numbers as $value) {
echo $value . "\n";
}
Output:
10
30
40
Best Practices
- Use
foreachinstead of a numericforloop whenever you don’t specifically need the index — it’s safer against key gaps and usually more readable. - After
unset(),array_filter(), or any operation that can remove elements, callarray_values()if you need the array to be sequential again. - Use
$array[] = $valueto append rather than manually tracking and assigning the “next” index yourself. - Use
sort()when you want both re-ordered values and re-indexed keys; useasort()if you need to preserve the original keys while sorting by value. - Check
isset($array[$i])orarray_key_exists($i, $array)before accessing an index you aren’t sure exists, especially with user-supplied or externally-sourced indexes. - Prefer
[]short array syntax overarray()for new code — it’s the modern standard and slightly less to type. - Don’t rely on numeric key order matching insertion order after mixing explicit and automatic indexes — if order matters, sort explicitly.
Practice Exercises
- Exercise 1: Create an indexed array of 5 integers. Write a loop that prints only the even numbers along with their original index.
- Exercise 2: Start with
$letters = ["a", "b", "c", "d", "e"];, remove the element at index 2 withunset(), then write code that safely re-indexes the array and prints every letter with its new index. - Exercise 3: Given
$temperatures = [72, 68, 75, 90, 61];, write a script that finds and prints the highest and lowest temperature without usingsort()— onlyforeachand comparisons. (Hint: track two variables as you loop.)
Summary
- An indexed array is just a PHP array whose keys are sequential integers starting at 0 by default.
- Internally, all PHP arrays are ordered hash maps; “indexed” describes the keys you chose to use, not a separate data type.
- PHP tracks a “next free index” counter so
$array[] = $valuealways appends in O(1) time. - Iteration order follows insertion order, not numeric key order — sort explicitly if you need numeric order.
- Removing elements with
unset()creates gaps in the keys; usearray_values()orforeachto avoid index-related bugs. count()gives the number of elements, not the last valid index — the last index iscount($array) - 1.
