PHP Arrays — Off-by-One Errors That Null Prices
A count($items) off-by-one error caused null prices on every 10th product.
- PHP arrays store multiple values under a single variable using integer or string keys
- Indexed arrays: auto-numbered from 0, best for ordered lists
- Associative arrays: custom string keys, ideal for structured records
- Multidimensional arrays: arrays inside arrays, like a spreadsheet
- Copy-on-write means assigning an array to another variable doesn't duplicate it until you modify one
- Biggest mistake: off-by-one from zero-based indexing or using loose comparison (==) when strict (===) is needed
Imagine you're packing for a road trip. Instead of carrying one item in each hand, you grab a suitcase and pack everything inside — shirts in one slot, shoes in another, snacks in a zip pocket. A PHP array is that suitcase: one variable that holds multiple values, each stored in its own labelled slot. Without arrays, you'd need a separate variable for every piece of data — like carrying each item of clothing loose in your arms. Nobody wants that.
Every real application deals with lists. A shopping cart holds multiple products. A blog has multiple posts. A user profile stores multiple preferences. If PHP only let you store one value per variable, you'd be writing hundreds of lines just to hold a handful of items — and your code would collapse the moment the list grew or shrank. Arrays are how PHP solves this problem, and they're used in virtually every PHP script ever written.
The specific problem arrays fix is called 'data grouping'. Instead of writing $product1, $product2, $product3 and scrambling to keep track of them all, you write one $products array and let PHP manage the collection for you. You can add to it, remove from it, loop over it, sort it, search it — all with built-in tools PHP provides out of the box.
By the end of this article you'll know how to create all three types of PHP arrays (indexed, associative, and multidimensional), how to read and update values inside them, how to loop through them, and — crucially — the exact mistakes that trip up beginners so you can dodge them from day one.
Indexed Arrays — A Numbered List PHP Can Remember
An indexed array stores values in numbered slots, starting at position 0. Think of it like a numbered queue at a bakery: the first person is ticket 0, the second is ticket 1, and so on. The number is called the index or key, and the thing stored there is the value.
Why does counting start at 0? It's a convention inherited from low-level computing, and PHP follows it like almost every other language. It trips up beginners exactly once — then you never forget it.
You create an indexed array using the square-bracket syntax (preferred in modern PHP) or the older array() function. Both work, but square brackets are cleaner and what you'll see in professional codebases today.
To get a value out, you write the variable name followed by the index in square brackets: $fruits[0] gives you the first item. To add a new item to the end, use $fruits[] = 'mango' — the empty brackets tell PHP to append automatically. You don't need to know what the next index number is; PHP figures it out.
end() function.array_key_first() if you're not sure.end() for the last element.Associative Arrays — Give Your Data a Meaningful Label
An indexed array is great for ordered lists, but what if you want to store a person's profile? You'd need to remember that index 0 is the name, index 1 is the email, index 2 is the age — and that's a disaster waiting to happen.
Associative arrays solve this by letting you choose your own keys instead of relying on numbers. Think of it like a form with labelled fields: 'Name: Sarah', 'Email: sarah@example.com', 'Age: 28'. Each label (key) maps directly to its value. This is how PHP stores structured data — user records, configuration settings, API responses.
You create an associative array using the same square-bracket syntax, but you define the key explicitly using the => arrow operator. On the left of => is the key (a string), on the right is the value.
Associative arrays are the backbone of most real PHP applications. When PHP reads a submitted HTML form, it hands you the data as an associative array in $_POST. When you decode a JSON API response, you get an associative array. When you fetch a database row with PDO, it comes back as an associative array. You'll use these constantly.
Multidimensional Arrays — Arrays Inside Arrays
So far each array has stored simple values — strings and numbers. But what if you want to store a list of users, and each user has their own profile data? You put arrays inside arrays. That's a multidimensional array, and it's less scary than it sounds.
Think of a spreadsheet. Each row is a user, each column is a piece of data (name, email, age). A multidimensional array works exactly the same way: the outer array is the list of rows, and each inner array is one row of data.
To access a value, you chain square brackets: $users[0]['name'] means 'go to the first user (index 0), then get the name key from their profile'. It reads left-to-right, outer-to-inner.
Multidimensional arrays are everywhere in PHP. Database query results return as an array of associative arrays. JSON from an API decodes into nested arrays. Shopping cart data is a list of product arrays. Once you're comfortable with one level of nesting, the rest follows the exact same logic — just add another bracket.
The Most Useful PHP Array Functions You'll Actually Use
PHP ships with over 70 built-in array functions. You don't need to memorise all of them — but a handful come up in almost every project. Knowing these saves you from writing loops by hand for tasks PHP already solved.
Here's the honest shortlist: count() tells you how many items are in an array. in_array() checks whether a value exists anywhere in an array. array_push() adds items to the end (though the [] shorthand is more common). array_pop() removes and returns the last item. array_merge() combines two arrays into one. array_filter() removes items that don't pass a test. array_map() transforms every item using a function. sort() sorts an indexed array alphabetically or numerically. ksort() sorts an associative array by its keys.
The most important mental model: some functions return a new array (array_map, array_filter, array_merge) while others modify the original array in place (sort, ksort, array_push). This distinction matters — if you expect a sorted copy but get NULL because you forgot to use the return value, you'll be confused for longer than you should be.
PHP Array Functions Quick Reference Table
Here is a quick reference table of the most commonly used PHP array functions. Keep this handy while coding.
| Function | Description | Return Value | Modifies Original? |
|---|---|---|---|
| --- | --- | --- | --- |
count($arr) | Returns the number of elements in an array | int | No |
in_array($needle, $haystack, $strict) | Checks if a value exists in an array | bool | No |
array_push($arr, ...$values) | Adds one or more elements to the end of an array | int (new length) | Yes |
array_pop($arr) | Removes and returns the last element | mixed | Yes |
array_merge(...$arrays) | Merges one or more arrays into one | array | No |
array_search($needle, $haystack, $strict) | Searches array for a given value and returns the first key | mixed (key or false) | No |
array_map($callback, $arr) | Applies a callback to every element; returns a new array | array | No |
array_filter($arr, $callback) | Filters elements via a callback; returns a new array | array | No |
array_reduce($arr, $callback, $initial) | Iteratively reduces the array to a single value via a callback | mixed | No |
sort($arr) | Sorts an indexed array in ascending order | bool (true on success) | Yes |
rsort($arr) | Sorts an indexed array in descending order | bool | Yes |
ksort($arr) | Sorts an associative array by key in ascending order | bool | Yes |
krsort($arr) | Sorts an associative array by key in descending order | bool | Yes |
asort($arr) | Sorts an associative array by value in ascending order, preserving keys | bool | Yes |
arsort($arr) | Sorts an associative array by value in descending order, preserving keys | bool | Yes |
array_unique($arr, $flags) | Removes duplicate values from an array | array | No (returns new) |
array_key_exists($key, $arr) | Checks if the specified key exists in the array | bool | No |
array_keys($arr) | Returns all the keys of an array | array | No |
array_values($arr) | Returns all the values of an array | array | No |
implode($glue, $pieces) | Joins array elements with a string (aliased as join) | string | No |
Keep in mind that the $strict parameter for in_array and array_search defaults to false (loose comparison). Always pass true unless you intentionally need type coercion.
array_push and array_pop modify in place — but the pattern holds for array_map, array_filter, array_merge, array_reduce, array_unique, array_keys, array_values. If the function name begins with array_ and ends with something other than push/pop/shift/unset, it generally returns a new array without touching the original.sort, rsort, ksort, asort, arsort, array_push, or array_pop is assigned to a variable — that's almost always wrong.Array Destructuring — Extract Array Values into Variables
PHP's array destructuring (or unpacking) lets you assign array elements directly to variables in one statement. This is a clean alternative to manual indexing and is especially useful when working with functions that return arrays, like database fetches or pathinfo().
PHP offers two syntaxes: the classic function (available since PHP 4) and the shorter square bracket syntax (since PHP 7.1). Both do the same thing, but the list()[] syntax is now preferred for its brevity and clarity.
Destructuring works with both indexed and associative arrays. For indexed arrays, PHP assigns variables in order starting from index 0. For associative arrays, you specify the key as the variable name, making it extremely readable.
One common gotcha: only assigns from numeric indices by default. To destructure associative arrays, you must use the list()[] syntax with named keys (PHP 7.1+). Also, you can skip elements by leaving a blank — [, $b, , $d] = $arr — which is great when you only need specific items.
[] syntax is shorter, more intuitive, and supports associative destructuring. If you need to support PHP 7.0 or earlier, stick with list(). But in any modern codebase (PHP 7.1+), the square bracket approach is the standard.list() extracts array values directly into variables. Use [] for PHP 7.1+ and take advantage of associative key matching to improve readability.Sorting Arrays in PHP (sort, rsort, ksort, usort)
PHP provides many sorting functions to arrange array elements in a specific order. The key is choosing the right function for your array type and desired outcome.
For indexed arrays, sorts in ascending order, and sort() sorts in descending order. Both discard existing keys and re-index numerically. This is fine when you don't care about preserving the original keys.rsort()
For associative arrays, sorts by key (ascending) and ksort() by key (descending), preserving key-value associations. krsort() sorts by value (ascending) and asort() by value (descending), also preserving keys. These are essential when you need to maintain logical relationships (e.g., sorting a user array by name while keeping each person's full data intact).arsort()
For custom sorting logic, accepts a user-defined comparison function. You can sort by any criteria — by the length of a string, by a nested property, or by a computed value. The comparison function must return an integer less than, equal to, or greater than zero.usort()
All sorting functions modify the array in place and return a boolean. They do not return a sorted copy. The only way to keep the original order is to clone the array first: $sorted = $arr; sort($sorted);.
$copy = $arr; sort($copy);. This is the most common sorting mistake in PHP.usort with a complex callback can be slow. For stable sorting, consider array_multisort.sort()/rsort() for indexed arrays, ksort()/asort() for associative arrays, and usort() for custom logic. All modify in place — copy first if you need the unsorted version.Spread Operator in Arrays (PHP 7.4+)
Introduced in PHP 7.4, the spread operator (...) allows you to unpack an array into another array. This is similar to but with a cleaner syntax and better performance in some cases.array_merge()
You use it inside a new array literal by prefixing the array variable with .... All values from the spread array are copied at that position. You can use the spread operator multiple times and combine it with regular elements.
One important behaviour: the spread operator only works with arrays and objects that implement Traversable (like ArrayIterator). It does not work with strings or non-traversable objects.
Another difference from : with string keys, the spread operator behaves identically — later keys overwrite earlier ones. With integer keys, array_merge() re-indexes them starting from 0, while the spread operator preserves integer keys from the source array (and if there's a conflict, the later key overwrites the earlier one, just like string keys). This subtle difference matters when merging indexed arrays where you want to keep original indices.array_merge()
Practice Exercises: Test Your PHP Array Skills
Apply what you've learned with these five real-world exercises. Each exercise comes with a description, hints, and the expected output. Try to solve each one without peeking at the solution code first.
Exercise 1: Shopping Cart Manipulation You have an array of product names in a cart. Write a function that adds a product to the cart, removes a product by name, and returns the updated cart. Assume products can appear multiple times (different quantities), but for this exercise each product appears once. Use array_push or [] to add, array_search to find the position, and unset plus array_values to re-index.
Exercise 2: Grade Sorter Given an associative array of student names and their integer grades (e.g., ['Alice' => 85, 'Bob' => 92, 'Carol' => 78]), sort the array by grade in descending order while preserving the student names as keys. Use . Then display each student's name and grade.arsort()
Exercise 3: Array Deduplication Write a function that removes duplicate values from an array while keeping the first occurrence of each value. Use . Test with a mix of integers and strings (with loose comparison issues). Then write a version that uses strict comparison. Compare results.array_unique()
Exercise 4: Nested Array Flattening Write a recursive function that flattens a multidimensional array into a single-level indexed array. For example, [1, [2, [3, 4]], 5] becomes [1, 2, 3, 4, 5]. Do not use array_merge_recursive — implement it manually using a foreach loop and recursion.
Exercise 5: Array Partitioning Given an array of numbers, partition it into two arrays: one with even numbers and one with odd numbers. Use with a callback or manually loop. Then output both arrays.array_filter()
```php <?php // --- Exercise 1: Shopping Cart --- $cart = ['apple', 'banana', 'cherry']; function addProduct(array $cart, string $product): array { $cart[] = $product; return $cart; } function removeProduct(array $cart, string $product): array { $index = array_search($product, $cart, true); if ($index !== false) { unset($cart[$index]); $cart = array_values($cart); // re-index } return $cart; } $cart = addProduct($cart, 'date'); $cart = removeProduct($cart, 'banana'); print_r($cart); // ['apple', 'cherry', 'date']
// --- Exercise 2: Grade Sorter --- $grades = ['Alice' => 85, 'Bob' => 92, 'Carol' => 78]; arsort($grades); foreach ($grades as $name => $grade) { echo "$name: $grade "; } // Bob: 92 // Alice: 85 // Carol: 78
// --- Exercise 3: Deduplication --- $data = [1, 2, 2, 3, '2', 4, 4]; $uniqueLoose = array_unique($data); // loose comparison $uniqueStrict = array_unique($data, SORT_STRING); // strict? Actually SORT_STRING still loose. Use array_values(array_unique($data, SORT_REGULAR))? // The correct way for strict: use manual loop or array_flip trick. // Simplified: array_unique with default (SORT_REGULAR) uses loose; to differentiate, you can use array_keys with strict? // For demonstration: echo "Loose unique: "; print_r($uniqueLoose); // [0=>1, 1=>2, 3=>3, 5=>4] (string '2' is considered equal to integer 2, so only first kept) // Strict: use manual: $strictUnique = []; foreach ($data as $value) { if (!in_array($value, $strictUnique, true)) { $strictUnique[] = $value; } } print_r($strictUnique); // [1,2,3,'2',4] — string '2' kept separate
// --- Exercise 4: Flatten --- function flatten(array $arr): array { $result = []; foreach ($arr as $item) { if (is_array($item)) { $result = array_merge($result, flatten($item)); } else { $result[] = $item; } } return $result; } $nested = [1, [2, [3, 4]], 5]; print_r(flatten($nested)); // [1,2,3,4,5]
// --- Exercise 5: Partition --- $numbers = [1, 2, 3, 4, 5, 6]; $even = []; $odd = []; foreach ($numbers as $num) { if ($num % 2 == 0) { $even[] = $num; } else { $odd[] = $num; } } echo "Even: " . implode(', ', $even) . " "; // 2,4,6 echo "Odd: " . implode(', ', $odd) . " "; // 1,3,5 ```
Try these exercises before reviewing the solution code. They will reinforce the array functions, sorting, destructuring, and traversal patterns covered in this article.
array_walk_recursive or a simple foreach. But understanding the underlying logic helps you debug when something unexpected happens.Array Traversal and Mutation Pitfalls
Looping over an array while modifying it leads to surprising bugs. The classic example is using foreach ($arr as &$value) — the reference persists after the loop ends, and any subsequent assignment to that variable modifies the array.
Another trap: calling on an element inside a unset()foreach loop can skip the next element or cause undefined behaviour depending on how the loop tracks the internal array pointer. PHP's foreach operates on a copy of the array by default, so unsetting inside the loop doesn't break the iteration — but it can confuse readers.
Using or array_shift() inside a loop is also dangerous because they reset the internal pointer. If you need to remove items during iteration, collect the keys first with array_pop(), then remove them after the loop.array_keys()
Type Juggling and Loose Comparison in Arrays
PHP is dynamically typed and famously loose with comparisons. When you use , in_array(), or array_search() without strict mode, type coercion can give you false positives or unexpected behaviour.array_unique()
Example: in_array('1', [1, 2, 3]) returns true because the string '1' is coerced to integer 1 during the loose comparison. This can cause security issues if you're checking user input against a whitelist.
Similarly, uses loose comparison by default, so array_unique()['1', 1, 2] becomes ['1', 2] — the integer 1 is considered equal to the string '1', and only the first occurrence is kept.
The fix is always to pass the third parameter true for strict comparison: in_array($needle, $haystack, true). For , use array_unique()SORT_REGULAR with the flag SORT_STRING or convert to a consistent type first.
true as third param to in_array, array_search, array_keys.Off-by-One Index Error Took Down the Pricing API at 2 PM
$items[count($items)] instead of $items[count($items) - 1] in a loop that assigned prices. When the loop hit the last index, it accessed out of bounds, returning NULL for that slot.$items[count($items)] with end($items) or use $items[count($items) - 1]. Add an explicit check for array emptiness before accessing any index.- Zero-based indexing is the single most common array bug in PHP.
- Never assume the last index equals the count — always subtract one.
- Use
orend()for the last element.array_key_last()
array_key_exists() or isset(). Remember: isset() returns false if the value is NULL, while array_key_exists() returns true even for NULL values.foreach ($arr as &$value), the reference persists after the loop. Add unset($value) immediately after the loop to break the reference.true to force strict type comparison: in_array($needle, $haystack, true).sort($arr); standalone, then use $arr. To keep the original, copy first: $sorted = $arr; sort($sorted);.Common mistakes to avoid
4 patternsOff-by-one index errors
Forgetting that sort() modifies the array in place
Using == instead of === when searching arrays
Foreach with &$value without unsetting after the loop
That's PHP Basics. Mark it forged?
13 min read · try the examples if you haven't