PHP Sorting Arrays
Sorting is one of the most common things you’ll do with PHP arrays: ordering a list of names alphabetically, ranking scores from highest to lowest, or arranging records by a custom rule. PHP doesn’t have just one sort() function – it has a whole family of them, because arrays in PHP are ordered maps that can have either sequential numeric keys or meaningful string keys, and different situations call for preserving or discarding those keys. Understanding which function to use, and what it does to your keys, is essential to avoiding subtle bugs.
Overview: How Array Sorting Works in PHP
Internally, a PHP array is an ordered hash table (the Zend engine calls this a HashTable). Every element is stored as a key-value pair, and the table also tracks the insertion/iteration order separately from the hash used for fast lookups. When you “sort” an array, PHP is not sorting a plain list of values – it’s reordering the internal bucket sequence of that hash table, and optionally renumbering the keys.
This distinction splits PHP’s sort functions into two families:
- Reindexing sorts –
sort(),rsort(),usort()– discard the existing keys entirely and replace them with fresh sequential integers starting at0. Use these when your array is really a list and the keys carry no meaning. - Key-preserving sorts –
asort(),arsort(),ksort(),krsort(),uasort(),uksort(),natsort(),natcasesort()– keep the original keys attached to their values, only changing the iteration order. Use these for associative arrays where the key (a name, an ID, a date) matters.
All of these functions sort the array in place by modifying the variable directly – that’s why the array argument must be an actual variable (it’s passed by reference), not the return value of a function call. Every one of these functions returns a plain bool (true on success), never the sorted array itself.
Comparison and sort flags
Functions like sort(), asort(), and ksort() accept an optional $flags argument that controls how values are compared: SORT_REGULAR (default, compares values using normal PHP comparison rules), SORT_NUMERIC (compares as numbers), SORT_STRING (compares as strings byte-by-byte), and SORT_NATURAL (“natural order”, so "img12" sorts after "img2" instead of before it, because the digit run is compared numerically). You can combine SORT_NATURAL or SORT_STRING with SORT_FLAG_CASE using the bitwise OR operator for case-insensitive comparisons.
Since PHP 8.0, every built-in sort function is stable: elements that compare as equal keep their original relative order. Before 8.0 this was only guaranteed for the key-preserving sorts, so if you support older PHP you may need to add a tiebreaker to a custom comparator.
Syntax
The general shape of every sort function is the same: pass the array by reference (and, for the u* family, a comparison callback), and PHP reorders it in place.
| Function | Sorts by | Keeps original keys? | Order |
|---|---|---|---|
sort() |
value | No (reindexed) | Ascending |
rsort() |
value | No (reindexed) | Descending |
asort() |
value | Yes | Ascending |
arsort() |
value | Yes | Descending |
ksort() |
key | Yes | Ascending |
krsort() |
key | Yes | Descending |
usort() |
value, via callback | No (reindexed) | Custom |
uasort() |
value, via callback | Yes | Custom |
uksort() |
key, via callback | Yes | Custom |
natsort() |
value, natural order | Yes | Ascending |
natcasesort() |
value, natural, case-insensitive | Yes | Ascending |
Signature pattern: sort(array &$array, int $flags = SORT_REGULAR): bool for the flag-based functions, and usort(array &$array, callable $callback): bool for the custom-comparator ones, where $callback takes two values and returns a negative, zero, or positive integer – almost always written today with the spaceship operator: fn($a, $b) => $a <=> $b.
Examples
Example 1: Basic value sorting with sort() and rsort()
<?php
$fruits = ["banana", "apple", "cherry", "date"];
sort($fruits);
print_r($fruits);
$numbers = [5, 3, 8, 1, 9];
rsort($numbers);
print_r($numbers);
Output:
Array
(
[0] => apple
[1] => banana
[2] => cherry
[3] => date
)
Array
(
[0] => 9
[1] => 8
[2] => 5
[3] => 3
[4] => 1
)
Notice both arrays end up with fresh integer keys 0 through n-1, even though they already had sequential keys – sort() and rsort() always reindex, regardless of what the keys looked like beforehand.
Example 2: Preserving keys with asort() and ksort()
<?php
$scores = [
"Alice" => 92,
"Bob" => 78,
"Carol" => 85,
];
asort($scores);
print_r($scores);
ksort($scores);
print_r($scores);
Output:
Array
(
[Bob] => 78
[Carol] => 85
[Alice] => 92
)
Array
(
[Alice] => 92
[Bob] => 78
[Carol] => 85
)
asort() reorders the entries by value (lowest score first) but each name stays attached to its own score. ksort() then reorders alphabetically by key instead, again without touching the key-value pairing.
Example 3: Custom multi-field sorting with usort()
<?php
$products = [
["name" => "Widget", "price" => 25.50, "stock" => 10],
["name" => "Gadget", "price" => 15.00, "stock" => 0],
["name" => "Gizmo", "price" => 15.00, "stock" => 5],
];
usort($products, fn($a, $b) => $a["price"] <=> $b["price"] ?: $b["stock"] <=> $a["stock"]);
foreach ($products as $product) {
echo "{$product['name']}: \${$product['price']} ({$product['stock']} in stock)\n";
}
Output:
Gizmo: $15 (5 in stock)
Gadget: $15 (0 in stock)
Widget: $25.5 (10 in stock)
The comparator sorts primarily by price (ascending). When two prices tie – Gadget and Gizmo both at 15.00 – the ?: short-circuit falls through to a secondary comparison on stock (descending), so Gizmo (5 in stock) appears before Gadget (0 in stock). This is the standard pattern for multi-column sorting in PHP: chain comparisons with ?:, each returning as soon as one produces a non-zero result.
How It Works Step by Step (Under the Hood)
- 1. Reference binding. PHP resolves the array argument by reference. This is why
sort(getArray())fails – only variables can be passed by reference, and a function’s return value isn’t a variable. - 2. Building a working list. The Zend engine walks the array’s internal hash table and builds a temporary list of bucket pointers to reorder.
- 3. Comparing elements. For flag-based functions, comparisons use PHP’s internal comparison rules (or the requested flag). For
u*functions, your callback is invoked for each comparison PHP’s algorithm needs – typically O(n log n) calls for n elements. - 4. Reordering, stably. Since PHP 8.0, the underlying algorithm guarantees a stable sort: if two elements compare equal, whichever appeared first in the original array still appears first afterward.
- 5. Key handling. If the function is a reindexing sort, PHP discards the old keys and assigns new ones (
0, 1, 2, ...) in the new order. Otherwise, it keeps the original keys attached to their values and only changes the bucket order used for iteration. - 6. Return value. The function returns
trueon success (orfalseon failure, e.g. if the argument wasn’t a valid array reference) – it does not return the sorted array.
Common Mistakes
Mistake 1: Using sort() on an associative array
sort() and rsort() always reindex. If your keys carry meaning, this silently throws them away.
<?php
$ages = ["Tom" => 30, "Ann" => 25, "Sue" => 40];
sort($ages);
print_r($ages);
Output:
Array
(
[0] => 25
[1] => 30
[2] => 40
)
The names are gone – only the sorted ages remain, under new integer keys. To sort by value while keeping each age tied to its name, use asort() instead:
<?php
$ages = ["Tom" => 30, "Ann" => 25, "Sue" => 40];
asort($ages);
print_r($ages);
Output:
Array
(
[Ann] => 25
[Tom] => 30
[Sue] => 40
)
Mistake 2: Assuming sort functions return the sorted array
Every sort function mutates its argument in place and returns a boolean. Capturing the return value as “the sorted array” is a very common bug for people coming from languages where sort() returns a new array.
<?php
$numbers = [42, 17, 89, 3];
$sorted = usort($numbers, fn($a, $b) => $a <=> $b);
echo gettype($sorted) . "\n";
print_r($sorted);
Output:
boolean
1
$sorted is just true (printed as 1 by print_r), not the array. The real sorted data lives in $numbers, which was modified in place:
<?php
$numbers = [42, 17, 89, 3];
usort($numbers, fn($a, $b) => $a <=> $b);
print_r($numbers);
Output:
Array
(
[0] => 3
[1] => 17
[2] => 42
[3] => 89
)
Best Practices
- Choose based on your data shape: reach for
sort()/rsort()/usort()for plain lists, and the key-preserving variants (asort(),ksort(),uasort(),uksort()) whenever the keys mean something. - Write custom comparators with the spaceship operator (
<=>) instead of manual subtraction orif/elseifchains – it’s shorter, correct for floats, and handles ties naturally when chained with?:. - Prefer a built-in
$flagsargument (SORT_STRING,SORT_NUMERIC,SORT_NATURAL) over a custom callback when possible – it’s faster and less error-prone than hand-rolled comparison logic. - Use
natsort()ornatcasesort()for human-friendly ordering of strings that contain numbers, like filenames (file2.txtbeforefile10.txt). - Remember every sort function returns
bool, not the array – never assign the return value expecting sorted data. - For sorting multiple related arrays together (parallel arrays, or by more than one column without a callback), consider
array_multisort(). - Keep comparator callbacks pure and cheap – they may be invoked many times during the sort, and a comparator that isn’t consistent (e.g. depends on external mutable state) can produce an incorrectly ordered result.
Practice Exercises
- Exercise 1: Given
$temps = ["Berlin" => 18, "Cairo" => 34, "Oslo" => 9, "Lima" => 21];, sort the cities from hottest to coldest while keeping each city name attached to its temperature, then print each line asCity: Temp. - Exercise 2: Given
$files = ["photo10.png", "photo2.png", "photo1.png", "Photo20.png"];, sort them into natural, case-insensitive order so a human would expectphoto1.png, photo2.png, photo10.png, Photo20.png. - Exercise 3: Given an array of associative arrays representing students (each with
nameandgrade), write ausort()callback that orders them bygradedescending, and for students with the same grade, bynamealphabetically ascending.
Summary
- PHP arrays are ordered hash tables, so “sorting” means reordering internal buckets – and sometimes renumbering keys.
sort(),rsort(), andusort()reindex the array with new integer keys;asort(),arsort(),ksort(),krsort(),uasort(), anduksort()preserve the original keys.natsort()andnatcasesort()give human-friendly ordering for strings mixed with numbers.- All built-in sorts are stable as of PHP 8.0, mutate the array in place by reference, and return a
bool– never the sorted array. - Custom comparators use the spaceship operator
<=>, and can be chained with?:for multi-field sorting. - Pick the function that matches whether your keys matter – this single decision prevents the most common sorting bugs in PHP.
