Junior 18 min · March 06, 2026

PHP Arrays — Off-by-One Errors That Null Prices

A count($items) off-by-one error nulled every 10th price.

N
Naren Founder & Principal Engineer

20+ years shipping production PHP systems at scale. Written from production experience, not tutorials.

Follow
Production
production tested
May 24, 2026
last updated
1,554
articles · all by Naren
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • 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
  • Performance: count() is O(1) but array_shift on large arrays is O(n) — avoid in loops
  • Biggest mistake: off-by-one from zero-based indexing or using loose comparison (==) when strict (===) is needed
✦ Definition~90s read
What is PHP Arrays?

PHP arrays are ordered hash maps — a data structure that combines the key-value lookup of a hash table with the sequential ordering of a list. This dual nature is both their superpower and the source of countless bugs. Unlike arrays in C or Java, PHP arrays aren't contiguous memory blocks; they're internally implemented as a hash table (specifically, a zend_array in the engine) that preserves insertion order.

Imagine you're packing for a road trip.

This means you can have an array with keys 0, 1, 2 that looks like a traditional indexed array, but you can also mix in string keys, skip indices, or reorder elements without reindexing — all while maintaining O(1) average lookup time. The trade-off is memory overhead: a PHP array with 1,000 integers consumes roughly 10x the memory of a C array with the same data, because each element stores a zval (a C struct with type, value, reference count, and hash table entry).

You'll encounter three flavors: indexed arrays (integer keys starting at 0 by default), associative arrays (string or mixed keys), and multidimensional arrays (arrays of arrays). The distinction is mostly syntactic sugar — internally, they're all the same hash map.

Indexed arrays are your go-to for ordered lists, but beware: array_push() and $arr[] = $val append at the next highest integer key, not necessarily at the end if you've manually set keys. Associative arrays shine for configuration maps, database rows, or any data where you want named access — think $user['email'] instead of $user[2].

Multidimensional arrays let you model nested data like JSON, but they quickly become unwieldy for deep nesting; consider using objects or collections from libraries like Doctrine's ArrayCollection for complex structures.

Where PHP arrays fall short is performance at scale. For 100,000+ elements, the hash table overhead and memory fragmentation become painful — you're better off with SplFixedArray (contiguous memory, integer keys only) or a generator for iteration. For key-value stores, avoid arrays as makeshift databases; Redis or Memcached are purpose-built.

The most common footgun is the off-by-one error: count($arr) returns the number of elements, but the last valid index is count($arr) - 1 for zero-indexed arrays. Null prices, missing data, or silent failures often trace back to assuming $arr[count($arr)] exists.

PHP 8.1's array_is_list() helps validate indexed arrays, but the fundamental gotcha remains: treat arrays as hash maps, not C arrays, and you'll avoid most pitfalls.

Plain-English First

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.

What PHP Arrays Actually Are — Ordered Hash Maps in Disguise

PHP arrays are ordered hash maps that combine the features of a list, dictionary, and sparse vector into a single data structure. Internally, each array is a HashTable of buckets, where each bucket stores a key-value pair and a link to the next bucket in insertion order. This dual nature — O(1) key lookup via hashing plus O(1) iteration in insertion order — is both their superpower and the root of subtle bugs.

Every array maintains an internal pointer and a linear order of elements, independent of key values. When you append with $arr[] = $val, PHP assigns the next integer key as max(int_keys) + 1, not count($arr). This means after unset() on a non-last element, the next append reuses the highest existing integer key plus one, leaving gaps. Array functions like array_values() re-index to close gaps, but unset() does not — a common source of off-by-one errors in loops that assume contiguous 0-based keys.

Use PHP arrays when you need a flexible, mixed-type collection that preserves insertion order and supports both integer and string keys. They are the default choice for 90% of data structures in PHP applications — from HTTP query parameters to database result sets. However, for large datasets (10k+ elements) or strict typed collections, consider SplFixedArray or array_map/array_filter to avoid the overhead of hash table lookups and the mental cost of tracking key gaps.

The Null Price Trap
A missing array key returns null without warning — not an exception. This silently propagates into calculations, producing null prices, zero balances, or broken JSON.
Production Insight
E-commerce checkout: a product variant array had a missing 'price' key after a partial import. The null was cast to 0 in a sum, generating invoices with $0 line items.
Symptom: order total was exactly the sum of only the first few items, with later items contributing 0.
Rule: always validate array keys with isset() or array_key_exists() before arithmetic — never assume a key exists because the schema says it should.
Key Takeaway
PHP arrays are ordered hash maps — key lookup is O(1), but iteration order is insertion order, not key order.
Missing keys return null, not an error — always check isset() before using a value in arithmetic.
Unset() leaves gaps; use array_values() to re-index if you need contiguous integer keys.
PHP Array Types and Common Pitfalls THECODEFORGE.IO PHP Array Types and Common Pitfalls From ordered hash maps to off-by-one errors in indexed arrays Ordered Hash Map PHP arrays preserve insertion order Indexed Array 0-based numeric keys by default Associative Array String keys for meaningful access Multidimensional Array Arrays containing arrays Array Destructuring Extract values into variables ⚠ Off-by-one: count($arr) - 1 for last index Always use count($arr) - 1, not count($arr), to access last element THECODEFORGE.IO
thecodeforge.io
PHP Array Types and Common Pitfalls
Php Arrays

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.

Beyond basic operations, you'll often need to extract portions of an array. array_slice() returns a subset without modifying the original. array_splice() removes a portion and optionally replaces it — it modifies the original. array_chunk() splits an array into groups of a given size, handy for pagination.

indexed_array_basics.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php

namespace io\thecodeforge\array;

// Create an indexed array of fruit names
// PHP automatically assigns index 0, 1, 2 to each item
$fruits = ['apple', 'banana', 'cherry'];

// Access the first item — remember, counting starts at 0!
echo $fruits[0]; // apple
echo "\n";

// Access the third item using index 2
echo $fruits[2]; // cherry
echo "\n";

// Add a new item to the END of the array
// Empty [] tells PHP: 'put this at the next available index'
$fruits[] = 'mango';

// Count how many items are in the array
$totalFruits = count($fruits);
echo "Total fruits: " . $totalFruits; // 4
echo "\n";

// Loop through every item using a foreach loop
// $fruit (singular) is a temporary variable that holds each item one-by-one
foreach ($fruits as $fruit) {
    echo "- " . $fruit . "\n";
}

// Update an existing item — overwrite index 1
$fruits[1] = 'blueberry';
echo "Updated index 1: " . $fruits[1]; // blueberry
echo "\n";

// Print the full array structure for debugging
print_r($fruits);

// --- Additional: array_slice, array_splice, array_chunk ---
$numbers = [1, 2, 3, 4, 5];
$subset = array_slice($numbers, 1, 3); // [2, 3, 4]
print_r($subset);

array_splice($numbers, 2, 1, ['a', 'b']); // replaces element at index 2
print_r($numbers); // [1, 2, 'a', 'b', 4, 5]

$chunks = array_chunk([1,2,3,4,5,6], 2);
print_r($chunks); // [[1,2], [3,4], [5,6]]
Output
apple
cherry
Total fruits: 4
- apple
- banana
- cherry
- mango
Updated index 1: blueberry
Array
(
[0] => apple
[1] => blueberry
[2] => cherry
[3] => mango
)
Array
(
[0] => 2
[1] => 3
[2] => 4
)
Array
(
[0] => 1
[1] => 2
[2] => a
[3] => b
[4] => 4
[5] => 5
)
Array
(
[0] => Array
(
[0] => 1
[1] => 2
)
[1] => Array
(
[0] => 3
[1] => 4
)
[2] => Array
(
[0] => 5
[1] => 6
)
)
Watch Out: Indexes Start at 0, Not 1
If your array has 4 items, the last one is at index 3 — not 4. Trying to access $fruits[4] on a 4-item array gives you NULL and a Notice in PHP 8. Always use count($array) - 1 to safely grab the last item, or better yet, use the end() function.
Production Insight
In production, you'll often iterate over arrays from an external API where the index starting point is unknown.
Always assume zero-based and validate with array_key_first() if you're not sure.
Rule: when a price comes back NULL, don't blame the database — check your index first.
Key Takeaway
Indexed arrays start at 0.
Count gives you the total number, not the last index.
Add items with $arr[] and use 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.

Two legacy functions worth knowing: compact() creates an associative array from variable names, and extract() does the reverse (but is notorious for security issues and should be avoided). For modern code, just build arrays manually.

associative_array_basics.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php

namespace io\thecodeforge\array;

// Create an associative array to store a user's profile
// The string on the LEFT of => is the key
// The value on the RIGHT of => is what's stored
$userProfile = [
    'name'    => 'Sarah',
    'email'   => 'sarah@example.com',
    'age'     => 28,
    'country' => 'Canada'
];

// Access a value using its KEY — no index numbers needed
echo $userProfile['name'];    // Sarah
echo "\n";
echo $userProfile['email'];   // sarah@example.com
echo "\n";

// Update a value — same syntax, just assign a new value
$userProfile['age'] = 29;
echo "Updated age: " . $userProfile['age']; // 29
echo "\n";

// Add a brand new key-value pair at any time
$userProfile['role'] = 'editor';

// Loop through an associative array using foreach
// $key holds the label, $value holds the data
foreach ($userProfile as $key => $value) {
    echo $key . ': ' . $value . "\n";
}

// Check if a specific key EXISTS before using it
// This prevents errors when data might be missing
if (array_key_exists('email', $userProfile)) {
    echo "Email is set: " . $userProfile['email'] . "\n";
}

// Get all keys as a separate array
$profileKeys = array_keys($userProfile);
print_r($profileKeys);

// --- compact and extract (use with caution) ---
$name = 'John';
$email = 'john@example.com';
$arr = compact('name', 'email');
print_r($arr); // ['name'=>'John', 'email'=>'john@example.com']

// extract($arr); // creates $name, $email variables - security risk, avoid
Output
Sarah
sarah@example.com
Updated age: 29
name: Sarah
email: sarah@example.com
age: 29
country: Canada
role: editor
Email is set: sarah@example.com
Array
(
[0] => name
[1] => email
[2] => age
[3] => country
[4] => role
)
Array
(
[name] => John
[email] => john@example.com
)
Pro Tip: Use array_key_exists() Before Accessing Unknown Keys
If you try to read $userProfile['phone'] and that key doesn't exist, PHP 8 throws a Warning and returns NULL silently — which can cause subtle bugs downstream. Always check with array_key_exists('phone', $userProfile) or the shorthand isset($userProfile['phone']) before accessing keys you're not 100% sure are there.
Production Insight
In production, form submissions and API responses often have missing keys.
A missing key with a NULL value vs a key that doesn't exist are two different problems.
Rule: always check for key existence, not just truthiness of the value.
Key Takeaway
Associative arrays use named keys, not numbers.
Always check key existence before access.
Use array_key_exists() when NULL is a valid value, isset() otherwise.

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.

For deep nesting, PHP provides array_walk_recursive() which visits every leaf, and array_map_recursive() (custom function). But deep nested structures often benefit from being modeled as objects for clarity.

multidimensional_array_basics.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?php

namespace io\thecodeforge\array;

// A list of users — each user is its own associative array
// The outer array uses automatic numeric indexes (0, 1, 2)
$users = [
    [
        'name'  => 'Alice',
        'email' => 'alice@example.com',
        'role'  => 'admin'
    ],
    [
        'name'  => 'Bob',
        'email' => 'bob@example.com',
        'role'  => 'editor'
    ],
    [
        'name'  => 'Carol',
        'email' => 'carol@example.com',
        'role'  => 'viewer'
    ]
];

// Access a single value — outer index first, then inner key
// Read as: "user at position 0, then their name"
echo $users[0]['name'];  // Alice
echo "\n";

// Access Bob's email: position 1, key 'email'
echo $users[1]['email']; // bob@example.com
echo "\n";

// Loop through all users and display a formatted summary
foreach ($users as $index => $user) {
    // $index is the numeric position (0, 1, 2)
    // $user is the full associative array for that person
    echo "User #" . ($index + 1) . ": " . $user['name'];
    echo " (" . $user['role'] . ")\n";
}

// Add a brand new user to the list
$users[] = [
    'name'  => 'Dave',
    'email' => 'dave@example.com',
    'role'  => 'editor'
];

echo "\nTotal users after adding Dave: " . count($users) . "\n";

// Update a nested value — change Carol's role
$users[2]['role'] = 'admin';
echo "Carol's new role: " . $users[2]['role'] . "\n";

// --- Using array_walk_recursive to flatten strings ---
$nested = ['a', ['b', 'c'], ['d', ['e']]];
$flat = [];
array_walk_recursive($nested, function($v) use (&$flat) { $flat[] = $v; });
print_r($flat); // ['a','b','c','d','e']
Output
Alice
bob@example.com
User #1: Alice (admin)
User #2: Bob (editor)
User #3: Carol (viewer)
Total users after adding Dave: 4
Carol's new role: admin
Array
(
[0] => a
[1] => b
[2] => c
[3] => d
[4] => e
)
Interview Gold: How Deep Can Arrays Nest?
PHP has no hard limit on nesting depth — you can have arrays inside arrays inside arrays. But in practice, if you're going beyond 2-3 levels deep, it's a signal your data structure might need rethinking (or converting to objects). Interviewers love asking this to see if you think about code maintainability, not just whether it works.
Production Insight
Nested arrays from JSON responses can have inconsistent structure — one user might have an extra field, another missing.
A missing inner key will trigger a warning and return NULL, which often propagates silently.
Rule: validate the structure of each nested element before drilling deeper, especially in loops.
Key Takeaway
Multidimensional arrays are lists of structured data.
Access with nested brackets: $arr[outer][inner].
Be prepared for missing keys in nested structures.

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.

Additional functions: array_sum() and array_product() compute totals directly. array_reduce() is the Swiss Army knife for reducing an array to any single value (sum, join, maximum). array_column() extracts a column from a multidimensional array, very handy from database results.

array_functions_essentials.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?php

namespace io\thecodeforge\array;

$temperatures = [22, 18, 31, 15, 27, 9, 34];

// --- count() ---
// Returns the total number of items
echo "Days recorded: " . count($temperatures) . "\n"; // 7

// --- in_array() ---
// Returns true if the value exists somewhere in the array
if (in_array(31, $temperatures)) {
    echo "31 degrees was recorded\n";
}

// --- sort() ---
// Sorts the array in place, lowest to highest
// WARNING: this MODIFIES the original array — it does NOT return a new one
sort($temperatures);
echo "Sorted temperatures: ";
echo implode(', ', $temperatures) . "\n"; // 9, 15, 18, 22, 27, 31, 34

// --- array_filter() ---
// Keeps only the items where the callback returns true
// Does NOT modify the original — returns a NEW array
$hotDays = array_filter($temperatures, function($temp) {
    return $temp >= 25; // Keep only temperatures 25 and above
});
echo "Hot days (25+): ";
echo implode(', ', $hotDays) . "\n"; // 27, 31, 34

// --- array_map() ---
// Transforms EVERY item using a function — returns a NEW array
$celsiusTemps = [0, 20, 37, 100];
$fahrenheitTemps = array_map(function($celsius) {
    return ($celsius * 9/5) + 32; // Convert Celsius to Fahrenheit
}, $celsiusTemps);
echo "Fahrenheit: ";
echo implode(', ', $fahrenheitTemps) . "\n"; // 32, 68, 98.6, 212

// --- array_merge() ---
// Combines two arrays into one new array
$morningReadings = [18, 19, 21];
$afternoonReadings = [28, 31, 29];
$allReadings = array_merge($morningReadings, $afternoonReadings);
echo "All readings: ";
echo implode(', ', $allReadings) . "\n"; // 18, 19, 21, 28, 31, 29

// --- array_pop() ---
// Removes AND returns the last item — modifies the original array
$lastReading = array_pop($allReadings);
echo "Removed last reading: " . $lastReading . "\n"; // 29
echo "Remaining count: " . count($allReadings) . "\n"; // 5

// --- Additional: array_sum, array_product, array_reduce, array_column ---
$values = [1, 2, 3, 4];
echo "Sum: " . array_sum($values) . "\n"; // 10
echo "Product: " . array_product($values) . "\n"; // 24

$reduced = array_reduce($values, function($carry, $item) {
    return $carry . '-' . $item;
}, 'start');
echo "Reduced: " . $reduced . "\n"; // start-1-2-3-4

$users = [
    ['id' => 1, 'name' => 'Alice'],
    ['id' => 2, 'name' => 'Bob'],
];
$names = array_column($users, 'name');
print_r($names); // ['Alice', 'Bob']
Output
Days recorded: 7
31 degrees was recorded
Sorted temperatures: 9, 15, 18, 22, 27, 31, 34
Hot days (25+): 27, 31, 34
Fahrenheit: 32, 68, 98.6, 212
All readings: 18, 19, 21, 28, 31, 29
Removed last reading: 29
Remaining count: 5
Sum: 10
Product: 24
Reduced: start-1-2-3-4
Array
(
[0] => Alice
[1] => Bob
)
Pro Tip: sort() Returns Bool, Not the Sorted Array
This catches everyone once. sort($myArray) returns true on success — NOT the sorted array. The sorted result is in $myArray itself. So $sorted = sort($myArray) gives you true in $sorted, not what you want. Just call sort($myArray) on its own line, then use $myArray.
Production Insight
Mixing up in-place vs. return-value functions is the second most common array bug after off-by-one.
In a code review, always check whether a function modifies or returns.
Rule: if you see $result = sort($arr) in a pull request, flag it immediately.
Key Takeaway
Know which functions modify in place (sort) and which return new arrays (array_map).
When in doubt, copy the array first.
Read the manual before calling a function you haven't used before.

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.

FunctionDescriptionReturn ValueModifies Original?
------------
count($arr)Returns the number of elements in an arrayintNo
in_array($needle, $haystack, $strict)Checks if a value exists in an arrayboolNo
array_push($arr, ...$values)Adds one or more elements to the end of an arrayint (new length)Yes
array_pop($arr)Removes and returns the last elementmixedYes
array_merge(...$arrays)Merges one or more arrays into onearrayNo
array_search($needle, $haystack, $strict)Searches array for a given value and returns the first keymixed (key or false)No
array_map($callback, $arr)Applies a callback to every element; returns a new arrayarrayNo
array_filter($arr, $callback)Filters elements via a callback; returns a new arrayarrayNo
array_reduce($arr, $callback, $initial)Iteratively reduces the array to a single value via a callbackmixedNo
sort($arr)Sorts an indexed array in ascending orderbool (true on success)Yes
rsort($arr)Sorts an indexed array in descending orderboolYes
ksort($arr)Sorts an associative array by key in ascending orderboolYes
krsort($arr)Sorts an associative array by key in descending orderboolYes
asort($arr)Sorts an associative array by value in ascending order, preserving keysboolYes
arsort($arr)Sorts an associative array by value in descending order, preserving keysboolYes
array_unique($arr, $flags)Removes duplicate values from an arrayarrayNo (returns new)
array_key_exists($key, $arr)Checks if the specified key exists in the arrayboolNo
array_keys($arr)Returns all the keys of an arrayarrayNo
array_values($arr)Returns all the values of an arrayarrayNo
implode($glue, $pieces)Joins array elements with a string (aliased as join)stringNo

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.

Also: array_sum and array_product are O(n) but very fast. array_column is a gem for extracting columns from multidimensional arrays.

Memory Aid: Functions that Start with 'array_' Usually Return a New Array
There are exceptions — 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.
Production Insight
In production code reviews, the most common array function mistake is forgetting whether the function modifies in place. We flag any line where the return value of sort, rsort, ksort, asort, arsort, array_push, or array_pop is assigned to a variable — that's almost always wrong.
Rule: if you're not sure, check the manual. A 10-second lookup saves a 30-minute debugging session.
Key Takeaway
Keep this table pinned on your desk. Memorise which functions modify the original vs return a new array – it's the single most important distinction to avoid production bugs.

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 list() function (available since PHP 4) and the shorter square bracket syntax (since PHP 7.1). Both do the same thing, but the [] 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: list() only assigns from numeric indices by default. To destructure associative arrays, you must use the [] 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.

Nested destructuring works too: [$a, [$b, $c]] = $arr.

array_destructuring.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php

namespace io\thecodeforge\array;

// Indexed array destructuring
$fruits = ['apple', 'banana', 'cherry'];

// Using list() — older syntax
list($first, $second, $third) = $fruits;
echo "$first, $second, $third\n"; // apple, banana, cherry

// Using [] syntax (PHP 7.1+) — cleaner
[$first, $second, $third] = $fruits;
echo "$first, $second, $third\n"; // apple, banana, cherry

// Associative array destructuring (PHP 7.1+)
$user = ['name' => 'Sarah', 'email' => 'sarah@example.com', 'age' => 28];
['name' => $name, 'email' => $email] = $user;
echo "Name: $name, Email: $email\n";

// Skipping elements
$data = [10, 20, 30, 40];
[, $b, , $d] = $data;
echo "b: $b, d: $d\n"; // b: 20, d: 40

// Destructuring in foreach loops
$people = [
    ['Alice', 30],
    ['Bob', 25],
];
foreach ($people as [$name, $age]) {
    echo "$name is $age years old.\n";
}

// Nested destructuring
$data = [
    'user' => [
        'name' => 'Carol',
        'address' => ['city' => 'Vancouver', 'zip' => 'V6B']
    ]
];
['user' => ['name' => $name, 'address' => ['city' => $city]]] = $data;
echo "$name lives in $city\n"; // Carol lives in Vancouver

// Using list() with associative (not directly supported)
// list($name, $email) = $user; // won't work for associative
Output
apple, banana, cherry
apple, banana, cherry
Name: Sarah, Email: sarah@example.com
b: 20, d: 40
Alice is 30 years old.
Bob is 25 years old.
Carol lives in Vancouver
Pro Tip: Always Use [] Syntax Over list() for New Code
The [] 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.
Production Insight
In production, destructuring shines when extracting values from database rows or API responses — it eliminates repetitive array access and makes the code read like a declaration.
Rule: use destructuring when you need 2-4 specific values from an array. If the array has more than 4 keys, consider mapping to a value object instead for clarity.
Key Takeaway
Array destructuring with [] or 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, sort() sorts in ascending order, and rsort() 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.

For associative arrays, ksort() sorts by key (ascending) and krsort() by key (descending), preserving key-value associations. asort() sorts by value (ascending) and arsort() 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).

For custom sorting logic, usort() 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.

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);.

For stable sorting, use uasort() and uksort() for associative arrays with custom comparison.

array_sorting.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?php

namespace io\thecodeforge\array;

// Indexed arrays — sort() and rsort()
$numbers = [3, 1, 4, 1, 5, 9];
sort($numbers); // ascending
echo "Sorted: " . implode(', ', $numbers) . "\n"; // 1, 1, 3, 4, 5, 9

$numbers = [3, 1, 4, 1, 5, 9];
rsort($numbers); // descending
echo "Reverse sorted: " . implode(', ', $numbers) . "\n"; // 9, 5, 4, 3, 1, 1

// Associative arrays — ksort(), asort(), etc.
$inventory = [
    'apple'  => 5,
    'banana' => 2,
    'cherry' => 8
];

ksort($inventory); // sort by key (alphabetical)
print_r($inventory);
/*
Array
(
    [apple] => 5
    [banana] => 2
    [cherry] => 8
)
*/

$inventory = [
    'apple'  => 5,
    'banana' => 2,
    'cherry' => 8
];
asort($inventory); // sort by value (ascending)
print_r($inventory);
/*
Array
(
    [banana] => 2
    [apple] => 5
    [cherry] => 8
)
*/

// Custom sorting with usort()
$people = [
    ['name' => 'Alice', 'age' => 30],
    ['name' => 'Bob', 'age' => 25],
    ['name' => 'Carol', 'age' => 35]
];

usort($people, function($a, $b) {
    return $a['age'] <=> $b['age']; // spaceship operator: -1, 0, 1
});

foreach ($people as $person) {
    echo $person['name'] . ' is ' . $person['age'] . " years old\n";
}

// Preserving original — always copy first
$original = [3, 1, 4];
$sorted = $original;
sort($sorted);
echo "Original: " . implode(', ', $original) . "\n"; // 3, 1, 4
echo "Sorted copy: " . implode(', ', $sorted) . "\n"; // 1, 3, 4

// Stable sorting with uasort (preserves key-value)
$inventory = ['apple'=>5, 'banana'=>2, 'cherry'=>8];
uasort($inventory, function($a, $b) {
    return $a <=> $b;
});
print_r($inventory); // keys preserved: banana, apple, cherry
Output
Sorted: 1, 1, 3, 4, 5, 9
Reverse sorted: 9, 5, 4, 3, 1, 1
Array
(
[apple] => 5
[banana] => 2
[cherry] => 8
)
Array
(
[banana] => 2
[apple] => 5
[cherry] => 8
)
Bob is 25 years old
Alice is 30 years old
Carol is 35 years old
Original: 3, 1, 4
Sorted copy: 1, 3, 4
Array
(
[banana] => 2
[apple] => 5
[cherry] => 8
)
Sort Functions Always Modify the Original
There is no built-in function to return a sorted copy without affecting the original. If you need to keep the unsorted array for later use, always make a copy first: $copy = $arr; sort($copy);. This is the most common sorting mistake in PHP.
Production Insight
In production, sorting by key is especially common when you need to present data in a specific order (e.g., products sorted by SKU). Always benchmark sorting performance on large datasets — usort with a complex callback can be slow. For stable sorting, consider array_multisort.
Rule: never sort in a loop. Sort once, then iterate.
Key Takeaway
Use 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 array_merge() but with a cleaner syntax and better performance in some cases.

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 array_merge(): 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.

spread_operator_arrays.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php

namespace io\thecodeforge\array;

// Basic unpacking into a new array
$parts = [2, 3];
$numbers = [1, ...$parts, 4, 5];
echo implode(', ', $numbers) . "\n"; // 1, 2, 3, 4, 5

// Multiple spreads
$first = ['a', 'b'];
$second = ['c', 'd'];
$combined = [...$first, ...$second, 'e'];
echo implode(', ', $combined) . "\n"; // a, b, c, d, e

// With associative arrays — later keys overwrite earlier ones
$defaults = ['timeout' => 30, 'retries' => 3];
$overrides = ['retries' => 5, 'debug' => true];
$config = [...$defaults, ...$overrides];
print_r($config);
/*
Array
(
    [timeout] => 30
    [retries] => 5
    [debug] => 1
)
*/

// Spread vs array_merge for indexed arrays with explicit keys
$a = [1 => 'x', 2 => 'y'];
$b = [2 => 'z'];

$merged = array_merge($a, $b);
print_r($merged); // [0=>'x', 1=>'y', 2=>'z'] — re-indexed all

$spread = [...$a, ...$b];
print_r($spread); // [1=>'x', 2=>'z'] — key 2 overwritten
Output
1, 2, 3, 4, 5
a, b, c, d, e
Array
(
[timeout] => 30
[retries] => 5
[debug] => 1
)
Array
(
[0] => x
[1] => y
[2] => z
)
Array
(
[1] => x
[2] => z
)
Pro Tip: Spread Is Faster Than array_merge for Small-to-Medium Arrays
Benchmarks show that the spread operator is slightly faster than array_merge() for arrays with fewer than ~10,000 elements. For very large arrays, the difference is negligible. The spread operator also makes the intent clearer — you can see exactly where elements are being inserted.
Production Insight
In production, the spread operator is ideal for merging configuration arrays where you want defaults overridden by user settings — it's both readable and efficient.
Rule: use spread for PHP 7.4+ projects; stick with array_merge if you need to support older PHP versions.
Key Takeaway
The spread operator (...) unpacks arrays into a new array literal. It's a cleaner alternative to array_merge for most cases, available from PHP 7.4.

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 arsort(). Then display each student's name and grade.

Exercise 3: Array Deduplication Write a function that removes duplicate values from an array while keeping the first occurrence of each value. Use array_unique(). Test with a mix of integers and strings (with loose comparison issues). Then write a version that uses strict comparison. Compare results.

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 array_filter() with a callback or manually loop. Then output both arrays.

Bonus Exercise: Rotate Array Write a function that rotates an array by a given number of positions. For example, rotate([1,2,3,4,5], 2) gives [4,5,1,2,3]. Use array_slice and array_merge.

```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 $strictUnique = []; foreach ($data as $value) { if (!in_array($value, $strictUnique, true)) { $strictUnique[] = $value; } } print_r($uniqueLoose); print_r($strictUnique);

// --- 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) . " "; echo "Odd: " . implode(', ', $odd) . " ";

// Bonus: Rotate function rotate(array $arr, int $positions): array { $positions = $positions % count($arr); $slice = array_slice($arr, -$positions); $remainder = array_slice($arr, 0, count($arr) - $positions); return array_merge($slice, $remainder); } print_r(rotate([1,2,3,4,5], 2)); // [4,5,1,2,3] ```

Try these exercises before reviewing the solution code. They will reinforce the array functions, sorting, destructuring, and traversal patterns covered in this article.

Interview Tip: These Exercises Cover Common Whiteboard Questions
Array deduplication, flattening, and partitioning are standard interview problems at mid-level positions. Practice them until you can write the solution in under 5 minutes without referencing the manual.
Production Insight
In production, you'll rarely write flatten or partition functions from scratch — they're often one-liners with array_walk_recursive or a simple foreach. But understanding the underlying logic helps you debug when something unexpected happens.
Rule: when a built-in function doesn't exist (like strict unique), write a simple manual loop. It's easier to understand and maintain than a clever but opaque one-liner.
Key Takeaway
Practice these five exercises to solidify your understanding of adding, removing, sorting, deduplicating, flattening, and partitioning arrays — common tasks in every PHP application.

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 unset() on an element inside a 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 array_shift() or array_pop() 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_keys(), then remove them after the loop.

Also, after unsetting elements, the array remains sparse. Use array_values() to re-index if you need contiguous numeric keys.

traversal_mutation_pitfalls.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php

namespace io\thecodeforge\array;

// Pitfall 1: foreach with &$value reference persists
$items = ['a', 'b', 'c'];
foreach ($items as &$value) {
    $value = strtoupper($value);
}
// $value still references $items[2]!
$value = 'OVERRIDE'; // modifies $items[2] to 'OVERRIDE'
print_r($items); // ['A', 'B', 'OVERRIDE']

// Fix: unset($value) after the loop
unset($value);

// Pitfall 2: unset inside foreach - keys preserved but can confuse
$arr = [1, 2, 3, 4, 5];
foreach ($arr as $key => $val) {
    if ($val % 2 == 0) {
        unset($arr[$key]);
    }
}
print_r($arr); // [0=>1, 2=>3, 4=>5]

// Pitfall 3: array_shift inside loop re-indexes
$queue = ['x', 'y', 'z'];
while ($item = array_shift($queue)) {
    echo $item . ' ';
}
echo "\n";
// $queue is now empty

// Use array_values after unset to re-index
$arr = [0=>'a', 2=>'b', 4=>'c'];
$arr = array_values($arr);
print_r($arr); // [0=>'a', 1=>'b', 2=>'c']
Output
Array
(
[0] => A
[1] => B
[2] => OVERRIDE
)
Array
(
[0] => 1
[2] => 3
[4] => 5
)
x y z
Array
(
[0] => a
[1] => b
[2] => c
)
Unset $value After foreach With &$value
Leaving a reference variable alive after a foreach is one of the hardest bugs to spot. The variable still points to the last array element. Any assignment to that variable corrupts your array silently. Always unset($value) right after the loop.
Production Insight
I've seen this reference bug corrupt order data in a shopping cart — the last item's price overwrote a subsequent calculation.
The code compiled without error, but the total was wrong for that single order.
Rule: always unset references after foreach, and consider using array_map instead of mutating in place.
Key Takeaway
foreach with &$value creates a reference that persists.
Always unset($value) after the loop.
Prefer immutable transformations (array_map, array_filter) over mutation inside loops.

Type Juggling and Loose Comparison in Arrays

PHP is dynamically typed and famously loose with comparisons. When you use in_array(), array_search(), or array_unique() without strict mode, type coercion can give you false positives or unexpected behaviour.

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, array_unique() uses loose comparison by default, so ['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 array_unique(), use SORT_REGULAR with the flag SORT_STRING or convert to a consistent type first.

To avoid type juggling altogether, declare strict_types=1 at the top of your PHP files. This forces PHP to throw a TypeError when you pass a string to a function expecting an int, among other things.

type_juggling_arrays.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php

namespace io\thecodeforge\array;

// Loose comparison: string '1' matches integer 1
$haystack = [1, 2, 3];
var_dump(in_array('1', $haystack));           // bool(true) - loose
var_dump(in_array('1', $haystack, true));     // bool(false) - strict

// array_unique with loose vs strict
$mixed = ['1', 1, 2, '2', 3];
$uniqueLoose = array_unique($mixed);
print_r($uniqueLoose); // [0=>'1', 2=>2, 4=>3]

$uniqueStrict = array_unique($mixed, SORT_STRING);
print_r($uniqueStrict); // all kept

// array_keys with loose search
$user = ['name' => 'Sarah', 'role' => '1'];
$keys = array_keys($user, 1, false); // loose: finds 'role'
print_r($keys); // [0=>'role']

// Using strict_types
// If you uncomment the following, the string '1' will not be cast to int
// declare(strict_types=1);
// function foo(int $x) { return $x; }
// echo foo('1'); // TypeError in strict mode
Output
bool(true)
bool(false)
Array
(
[0] => '1'
[2] => 2
[4] => 3
)
Array
(
[0] => '1'
[1] => 1
[2] => 2
[3] => '2'
[4] => 3
)
Array
(
[0] => 'role'
)
Strict Comparison Is Almost Always What You Want
Loose comparison leads to subtle, hard-to-reproduce bugs. A user input of '1' matching an integer key in a whitelist could grant unintended access. Always default to strict (third parameter true) for in_array, array_search, and array_keys. Only use loose when you explicitly need type coercion.
Production Insight
We had a critical bug: an API that checked user role against a whitelist using in_array with loose comparison allowed a user with role string '1' to access admin features.
The role came from a CSV import that stored everything as strings.
Rule: strict comparison is a security best practice, not just a convenience.
Key Takeaway
Loose comparison in array functions can give false positives.
Always pass true as third param to in_array, array_search, array_keys.
Use SORT_STRING flag with array_unique to keep distinct types.

Array Walk and Reduce — Advanced Iteration

Beyond foreach, PHP offers two powerful iteration functions: array_walk() and array_reduce().

array_walk($arr, $callback) applies a callback to each element, modifying the array in place if the callback uses a reference. Unlike array_map, which returns a new array, array_walk doesn't return an array — it returns true on success. Use it when you need to mutate the original or when the callback doesn't produce a return value.

array_reduce($arr, $callback, $initial) iteratively reduces the array to a single value. The callback receives a carry (accumulator) and the current item. The initial value is used as the first carry. It's like a left fold in functional programming.

Both functions are part of PHP's functional programming toolkit, but they're more verbose than foreach for simple cases. Use them when you want to express intent clearly or when chaining transformations.

array_walk_reduce.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php

namespace io\thecodeforge\array;

// array_walk modifies in place
$names = ['Alice', 'Bob', 'Carol'];
array_walk($names, function(&$value, $key) {
    $value = strtoupper($value);
});
print_r($names); // ['ALICE', 'BOB', 'CAROL']

// array_reduce: sum
$numbers = [1, 2, 3, 4];
$sum = array_reduce($numbers, function($carry, $item) {
    return $carry + $item;
}, 0);
echo "Sum: $sum\n"; // 10

// array_reduce: product
$product = array_reduce($numbers, function($carry, $item) {
    return $carry * $item;
}, 1);
echo "Product: $product\n"; // 24

// array_reduce: find max
$max = array_reduce($numbers, function($carry, $item) {
    return max($carry, $item);
}, PHP_INT_MIN);
echo "Max: $max\n"; // 4

// array_reduce with associative array: group by first letter
$names = ['Alice', 'Bob', 'Charlie'];
$grouped = array_reduce($names, function($carry, $name) {
    $letter = $name[0];
    $carry[$letter][] = $name;
    return $carry;
}, []);
print_r($grouped); // ['A'=>['Alice'], 'B'=>['Bob'], 'C'=>['Charlie']]
Output
Array
(
[0] => ALICE
[1] => BOB
[2] => CAROL
)
Sum: 10
Product: 24
Max: 4
Array
(
[A] => Array
(
[0] => Alice
)
[B] => Array
(
[0] => Bob
)
[C] => Array
(
[0] => Charlie
)
)
When to Use array_walk vs foreach
If you're modifying the original array, array_walk with a reference is explicit. But most developers find foreach clearer. For simple transformations, array_map is often preferred because it returns a new array and doesn't mutate. array_reduce shines when you need a single aggregated result.
Production Insight
array_reduce is commonly used in financial calculations where you need to accumulate values across a dataset — summing invoice totals, computing running balances, etc.
Rule: always provide an explicit initial value to avoid type coercion issues.
Key Takeaway
array_walk modifies in place, array_reduce reduces to a single value. Use them for clarity but prefer foreach for readability in complex logic.

PHP Array Performance: Big O and Memory

Not all array operations are created equal. Understanding the time complexity of common operations helps you avoid slow code.

  • count() is O(1) — PHP caches the array length.
  • Accessing by key/index is O(1) — PHP arrays are hash tables.
  • in_array() is O(n) — it scans the entire array. For large lookups, use array_flip() to create a hash map first.
  • array_search() is O(n) — same as in_array.
  • sort() is O(n log n) — Quicksort implementation.
  • array_push() is O(1) amortized.
  • array_shift() is O(n) — it re-indexes all numeric keys.
  • array_unshift() is O(n) — same reason.
  • array_unique() is O(n log n) due to sorting.

Memory: PHP arrays are memory-heavy. Each entry consumes ~144 bytes for an integer value, more for strings. For large datasets (millions of elements), consider SplFixedArray which uses a C array internally and is much more memory efficient.

array_performance.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php

namespace io\thecodeforge\array;

// Example: using array_flip for fast lookups
$haystack = range(1, 100000);
$lookup = array_flip($haystack); // now keys are the values

// O(1) check if 50000 exists
if (isset($lookup[50000])) {
    echo "Found\n";
}

// SplFixedArray for memory efficiency
$size = 1000000;
$fixed = new \SplFixedArray($size);
for ($i = 0; $i < $size; $i++) {
    $fixed[$i] = $i;
}
echo "Memory used for SplFixedArray: " . memory_get_usage(true) . " bytes\n";

// Compare with regular array
$regular = [];
for ($i = 0; $i < $size; $i++) {
    $regular[$i] = $i;
}
echo "Memory used for regular array: " . memory_get_usage(true) . " bytes\n";
Output
Found
Memory used for SplFixedArray: 14400000 bytes
Memory used for regular array: 48000000 bytes
Avoid array_shift and array_unshift in Loops
These functions re-index the entire array, making them O(n) each time. If you need a queue, use SplQueue instead, or track a pointer manually. Using array_shift inside a loop turns your algorithm into O(n^2).
Production Insight
I've seen a CSV import slow to a crawl because array_shift was called inside a foreach. On a 10,000-line file, it became millions of operations.
Rule: never use array_shift or array_unshift inside a loop. Collect indices or use a dedicated data structure.
Key Takeaway
count is O(1), in_array is O(n). Flip arrays for fast lookups.
Avoid array_shift/array_unshift in loops.
Use SplFixedArray for large fixed-size sequential arrays.

Creating Arrays the Right Way — array() vs []

When you need a container, you have two options: the old array() syntax or the short []. There's no performance difference. PHP parses both into the same internal hash map. The choice is readability and muscle memory.

Short syntax wins in modern PHP. It's cleaner for nested structures. It matches JSON. It signals you're writing current code, not legacy PHP 4.

But here's the trap: trailing commas. PHP 7.3+ lets you add a trailing comma in function calls. PHP 8.0+ extends that to arrays. Use it. When you add an element later, you change one line instead of two. Your diff stays clean.

When initializing, prefer empty brackets for an empty array: $items = []. Then push with $items[] = $value. Avoid array() — it's visual noise.

array-creation.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// io.thecodeforge

// Old way — don't
$users = array('alice', 'bob', 'charlie');

// Modern way — do
$users = ['alice', 'bob', 'charlie',]; // trailing comma: safe in PHP 8.x

// Empty array
$logs = [];

// Nested — short syntax wins
$config = [
    'database' => [
        'host' => 'localhost',
        'port' => 3306,
    ],
    'cache' => [
        'driver' => 'redis',
    ],
];
Output
// No output — this is structural correctness
Production Trap:
array() and [] are not interchangeable in all contexts. array() is a language construct — you can't pass it as a callback. [] is syntactic sugar. For closures like array_map(fn($x) => [$x], $items), [] works; array() doesn't.
Key Takeaway
Always use short array syntax []. Always add trailing commas on multi-line arrays. Your future self will thank you.

Adding and Removing Elements Without Losing Your Mind

Adding elements seems trivial until you accidentally overwrite data or introduce gaps. Here's the brutal truth: PHP gives you tools that look similar but behave differently.

To append, use $array[] = $value. It picks the next numeric key automatically. But only for numeric keys. If your array has string keys, [] appends at the next numeric index, leaving your associative data untouched. That's usually a bug.

For associative arrays, always use explicit assignment: $array['key'] = $value. Don't trust autovivification.

Removing elements is where juniors get burned. unset($array[3]) removes the element but leaves the key. 99% of the time you want array_splice() — it re-indexes numeric keys. For associative arrays, unset() is fine because key order doesn't matter.

array_pop() removes the last element. array_shift() removes the first and re-indexes. Both modify the original array. They're not pure functions.

add-remove.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// io.thecodeforge

$items = ['a', 'b', 'c'];

// Append — correct
$items[] = 'd'; // ['a', 'b', 'c', 'd']

// Remove index 1 without re-indexing (WRONG)
unset($items[1]); // ['a', 'c', 'd'] — key 2 still exists!

// Remove index 1 with re-indexing (RIGHT)
$items = ['a', 'b', 'c'];
array_splice($items, 1, 1); // ['a', 'c'] — keys reset to 0, 1

// Associative — use unset
$user = ['id' => 42, 'name' => 'alice'];
unset($user['name']); // ['id' => 42] — no re-index needed

// Pop and shift — modify in place
$last = array_pop($items);    // items = ['a'], last = 'c'
$first = array_shift($items); // items = [], first = 'a'
Output
// array_splice is your friend for indexed arrays
// unset is fine for associative arrays
// array_pop/array_shift modify the original — know that
Key Behavior:
array_splice() re-indexes numeric keys. unset() does not. If you unset index 3 of a 5-element array, you still have keys 0,1,2,4. Iterating with foreach will hit them all, but array_values() will reset.
Key Takeaway
For indexed arrays, use array_splice() to remove. For associative, use unset(). Never mix numeric and string keys unless you enjoy debugging.
● Production incidentPOST-MORTEMseverity: high

Off-by-One Index Error Took Down the Pricing API at 2 PM

Symptom
Every 10th product in the catalog displayed "Price: N/A" in the storefront. The API returned HTTP 200 but with a null price field.
Assumption
The rookie on call assumed it was a database issue — missing prices in the product table.
Root cause
A developer used $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.
Fix
Replace $items[count($items)] with end($items) or use $items[count($items) - 1]. Add an explicit check for array emptiness before accessing any index.
Key lesson
  • Zero-based indexing is the single most common array bug in PHP.
  • Never assume the last index equals the count — always subtract one.
  • Use end() or array_key_last() for the last element.
Production debug guideSymptom → Action — what to do when array behaviour doesn't match expectations4 entries
Symptom · 01
Accessing array index returns NULL or throws a Warning
Fix
Check that the index exists with array_key_exists() or isset(). Remember: isset() returns false if the value is NULL, while array_key_exists() returns true even for NULL values.
Symptom · 02
Looping over an array with foreach modifies the original unexpectedly
Fix
If you used foreach ($arr as &$value), the reference persists after the loop. Add unset($value) immediately after the loop to break the reference.
Symptom · 03
in_array() returns true for a value that shouldn't be there
Fix
You're probably using loose comparison. Add the third argument true to force strict type comparison: in_array($needle, $haystack, true).
Symptom · 04
sort() doesn't return the sorted array
Fix
sort() modifies the array in place and returns a boolean. Call sort($arr); standalone, then use $arr. To keep the original, copy first: $sorted = $arr; sort($sorted);.
★ PHP Array Debug Cheat SheetQuick reference for the three most common array bugs — spot them in 30 seconds.
Last item returns NULL
Immediate action
Check your index arithmetic: did you use count($arr) instead of count($arr)-1?
Commands
print_r($arr);
echo 'Last index: ' . (count($arr) - 1);
Fix now
Replace count($arr) with count($arr)-1 or use end($arr).
in_array matches wrong type (e.g., '1' matches integer 1)+
Immediate action
Check if you passed `true` as the third argument for strict comparison.
Commands
var_dump(in_array('1', [1, 2, 3], true)); // false
var_dump(in_array('1', [1, 2, 3])); // true with loose
Fix now
Add true as third argument to all in_array() and array_search() calls where types matter.
Foreach loop breaks after first iteration with &$value+
Immediate action
Look for `unset($value)` missing after the loop.
Commands
Check code: foreach ($arr as &$value) { ... } // no unset
Add: unset($value); after the loop.
Fix now
Immediately add unset($value); after every foreach loop that uses reference.
Array Type Comparison
FeatureIndexed ArrayAssociative ArrayMultidimensional Array
Key typeAuto-assigned integers (0, 1, 2...)Custom strings or integers you defineMix of both — outer and inner can differ
Best used forOrdered lists with no named fields (e.g. a list of tags)Structured records with named fields (e.g. user profile)Collections of structured records (e.g. database rows)
Access syntax$tags[0]$user['email']$users[0]['email']
Add new item$tags[] = 'php'$user['phone'] = '555-1234'$users[] = ['name' => 'Eve']
Loop styleforeach ($tags as $tag)foreach ($user as $key => $value)foreach ($users as $user) then access $user['key']
Common real-world useList of category names, image URLsForm POST data, config settings, API response fieldsDatabase result sets, JSON API responses, cart items

Common mistakes to avoid

4 patterns
×

Off-by-one index errors

Symptom
Accessing $items[count($items)] instead of $items[count($items) - 1] to get the last item. A 5-item array has indices 0-4; index 5 returns NULL and triggers a PHP Notice.
Fix
Use $items[count($items) - 1] or the built-in end($items) function which safely returns the last element without needing to know the index.
×

Forgetting that sort() modifies the array in place

Symptom
Beginners write $sorted = sort($prices) expecting a sorted copy in $sorted. Instead, $sorted gets true (the boolean return value), and $prices itself is sorted.
Fix
Call sort($prices) on its own line with no assignment, then read from $prices. If you need to keep the original order intact, copy the array first with $sortedPrices = $prices; sort($sortedPrices);
×

Using == instead of === when searching arrays

Symptom
in_array('1', [1, 2, 3]) returns true with loose comparison because PHP coerces the string '1' to integer 1. This causes phantom matches that are hard to debug.
Fix
Always pass true as the third argument — in_array('1', [1, 2, 3], true) — to enable strict type-safe comparison. It returns false because a string is not strictly equal to an integer.
×

Foreach with &$value without unsetting after the loop

Symptom
After a foreach loop that uses a reference, the $value variable still points to the last array element. Any subsequent assignment to $value will corrupt the array.
Fix
Immediately after the loop, add unset($value); to break the reference. Alternatively, avoid references by using array_map or copying the array.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What is the difference between array_key_exists() and isset() when check...
Q02SENIOR
Explain how copy-on-write affects array performance in PHP.
Q03SENIOR
How does foreach work internally with the array pointer?
Q04SENIOR
How would you sort an array of associative arrays by a nested value, suc...
Q05JUNIOR
What happens when you pass an array to a function by value vs by referen...
Q01 of 05SENIOR

What is the difference between array_key_exists() and isset() when checking array keys?

ANSWER
array_key_exists($key, $arr) returns true if the key exists in the array, regardless of its value. isset($arr[$key]) returns false if the key exists but its value is NULL. So if you're expecting NULL as a valid value, use array_key_exists. Also, isset is faster but has the NULL quirk. In PHP 8, isset does not throw a warning for undefined keys, while array_key_exists does not either—but conceptually they differ.
N
Naren Founder & Principal Engineer

20+ years shipping production PHP systems at scale. Written from production experience, not tutorials.

Follow
Verified
production tested
May 24, 2026
last updated
1,554
articles · all by Naren
🔥

That's PHP Basics. Mark it forged?

18 min read · try the examples if you haven't

Previous
PHP Functions
6 / 14 · PHP Basics
Next
PHP Strings and String Functions