Skip to content

10: PHP's Built-in Sorting Functions

10: PHP's Built-in Sorting Functions

PHP’s Built-in Sorting Functions Intermediate

Section titled “PHP’s Built-in Sorting Functions Intermediate”

PHP provides powerful built-in sorting functions that are highly optimized. In this chapter, we’ll explore all of PHP’s sorting functions, learn when to use each one, and master custom comparators for complex sorting requirements.

Estimated time: 55 minutes

By the end of this chapter, you will:

  • Master all PHP sorting functions: sort(), rsort(), asort(), ksort(), usort(), and more
  • Understand which underlying algorithms PHP uses (typically Quick Sort with optimizations)
  • Learn custom comparison functions with usort(), uasort(), and uksort()
  • Discover performance characteristics and when to use built-in vs custom implementations
  • Apply sorting to real-world PHP scenarios including objects, multidimensional arrays, and databases

Before starting this chapter, ensure you have:

  • ✓ Understanding of sorting algorithms (review Chapters 05-09 if needed)
  • ✓ Completion of Chapters 05-09 (305 mins if not done)
  • ✓ Familiarity with PHP arrays (10 mins review if needed)

Complete these hands-on tasks as you work through the chapter:

  • Test all basic PHP sorting functions (sort, rsort, asort, arsort, ksort, krsort)
  • Implement custom comparison functions with usort() for complex sorting rules
  • Sort objects by properties using usort() and arrow functions
  • Sort multidimensional arrays with array_multisort()
  • Benchmark PHP’s built-in sort() vs your custom implementations
  • Use array_column() with sorting for database-like operations
  • (Optional) Explore natsort() and natcasesort() for natural ordering

PHP has 15+ sorting functions! They differ in:

  • What they sort (values, keys, or both)
  • How they maintain keys
  • Sort order (ascending, descending, natural, user-defined)
FunctionSorts ByMaintains KeysOrderComplexityUse When
sort()Value❌ NoAscendingO(n log n)Indexed arrays
rsort()Value❌ NoDescendingO(n log n)Indexed arrays (reverse)
asort()Value✅ YesAscendingO(n log n)Associative arrays
arsort()Value✅ YesDescendingO(n log n)Associative arrays (reverse)
ksort()Key✅ YesAscendingO(n log n)Sort by keys
krsort()Key✅ YesDescendingO(n log n)Sort by keys (reverse)
usort()Value❌ NoUser-definedO(n log n)Custom comparisons
uasort()Value✅ YesUser-definedO(n log n)Custom + keep keys
uksort()Key✅ YesUser-definedO(n log n)Custom key comparison
natsort()Value✅ YesNaturalO(n log n)Human-friendly numbers
natcasesort()Value✅ YesNatural (case-ins)O(n log n)Natural + case-insensitive
array_multisort()MultipleOptionalMultipleO(n log n)Multi-column sorting
What do you need to sort?
├─ By VALUES
│ ├─ Need to keep array keys?
│ │ ├─ YES (associative array)
│ │ │ ├─ Ascending → asort()
│ │ │ ├─ Descending → arsort()
│ │ │ ├─ Natural order → natsort()
│ │ │ └─ Custom logic → uasort()
│ │ │
│ │ └─ NO (indexed array)
│ │ ├─ Ascending → sort()
│ │ ├─ Descending → rsort()
│ │ └─ Custom logic → usort()
│ │
├─ By KEYS
│ ├─ Ascending → ksort()
│ ├─ Descending → krsort()
│ └─ Custom logic → uksort()
└─ By MULTIPLE COLUMNS
└─ Use array_multisort()

Test: Sorting 10,000 elements

FunctionTimeNotes
sort()3.5msFastest for values
asort()4.2msSlight overhead for key preservation
ksort()3.8msSimilar to sort()
usort()15msCustom function overhead
uasort()16msCustom + key preservation
natsort()12msNatural comparison slower
array_multisort()8msMulti-column (2 columns)

Key Insights:

  • Built-in functions are highly optimized
  • Custom comparators (usort) are 3-4x slower
  • Natural sorting has moderate overhead
  • Pre-calculate expensive values before usort()
$numbers = [3, 1, 4, 1, 5, 9, 2, 6];
sort($numbers);
print_r($numbers);
// [1, 1, 2, 3, 4, 5, 6, 9]
$fruits = ['orange', 'apple', 'banana'];
sort($fruits);
print_r($fruits);
// ['apple', 'banana', 'orange']

Key points:

  • Sorts in ascending order
  • Resets array keys to 0, 1, 2…
  • Works with numbers and strings
  • Modifies array in place (returns bool, not sorted array)
$numbers = [3, 1, 4, 1, 5, 9];
rsort($numbers);
print_r($numbers);
// [9, 5, 4, 3, 1, 1]
$ages = [
'Alice' => 30,
'Bob' => 25,
'Charlie' => 35
];
asort($ages);
print_r($ages);
// [
// 'Bob' => 25,
// 'Alice' => 30,
// 'Charlie' => 35
// ]

Use when: You need to preserve key-value associations.

$scores = [
'Player1' => 100,
'Player2' => 150,
'Player3' => 75
];
arsort($scores);
print_r($scores);
// [
// 'Player2' => 150,
// 'Player1' => 100,
// 'Player3' => 75
// ]
$data = [
'z' => 1,
'a' => 2,
'm' => 3
];
ksort($data);
print_r($data);
// [
// 'a' => 2,
// 'm' => 3,
// 'z' => 1
// ]
$months = [
'March' => 3,
'January' => 1,
'February' => 2
];
krsort($months);
print_r($months);
// [
// 'March' => 3,
// 'February' => 2,
// 'January' => 1
// ]

Many sorting functions accept flags to control comparison behavior:

// Default comparison
sort($arr);
// Compare as numbers
sort($arr, SORT_NUMERIC);
// Compare as strings
sort($arr, SORT_STRING);
// Natural order
sort($arr, SORT_NATURAL);
// Case-insensitive string comparison
sort($arr, SORT_STRING | SORT_FLAG_CASE);
// Numeric strings
$numbers = ['10', '2', '1', '20'];
sort($numbers);
print_r($numbers); // ['1', '10', '2', '20'] - string comparison
sort($numbers, SORT_NUMERIC);
print_r($numbers); // ['1', '2', '10', '20'] - numeric comparison
// Case sensitivity
$words = ['Banana', 'apple', 'Cherry'];
sort($words);
print_r($words); // ['Banana', 'Cherry', 'apple'] - case-sensitive
sort($words, SORT_STRING | SORT_FLAG_CASE);
print_r($words); // ['apple', 'Banana', 'Cherry'] - case-insensitive
$numbers = [3, 1, 4, 1, 5, 9, 2, 6];
usort($numbers, function($a, $b) {
return $a <=> $b; // Spaceship operator (PHP 7+)
});
print_r($numbers); // [1, 1, 2, 3, 4, 5, 6, 9]

Comparison function rules:

  • Return < 0 if ashouldcomebeforea should come before b
  • Return 0 if aanda and b are equal
  • Return > 0 if ashouldcomeaftera should come after b
// These are equivalent:
function compare1($a, $b) {
if ($a < $b) return -1;
if ($a > $b) return 1;
return 0;
}
function compare2($a, $b) {
return $a <=> $b; // Much cleaner!
}
// For descending order:
function compareDesc($a, $b) {
return $b <=> $a; // Flip the order
}
class Student
{
public function __construct(
public string $name,
public int $grade,
public int $age
) {}
}
$students = [
new Student('Alice', 85, 20),
new Student('Bob', 92, 19),
new Student('Charlie', 85, 21),
];
// Sort by grade (descending)
usort($students, fn($a, $b) => $b->grade <=> $a->grade);
// Sort by grade (desc), then age (asc) for ties
usort($students, function($a, $b) {
$gradeCompare = $b->grade <=> $a->grade;
if ($gradeCompare !== 0) {
return $gradeCompare;
}
return $a->age <=> $b->age;
});
foreach ($students as $student) {
echo "{$student->name}: Grade {$student->grade}, Age {$student->age}\n";
}
$products = [
'laptop' => ['price' => 1200, 'rating' => 4.5],
'phone' => ['price' => 800, 'rating' => 4.8],
'tablet' => ['price' => 500, 'rating' => 4.2],
];
// Sort by rating, keep keys
uasort($products, function($a, $b) {
return $b['rating'] <=> $a['rating'];
});
print_r($products);
// [
// 'phone' => ['price' => 800, 'rating' => 4.8],
// 'laptop' => ['price' => 1200, 'rating' => 4.5],
// 'tablet' => ['price' => 500, 'rating' => 4.2],
// ]
$data = [
'item_3' => 'Third',
'item_1' => 'First',
'item_10' => 'Tenth',
'item_2' => 'Second',
];
// Natural sort by keys
uksort($data, function($a, $b) {
return strnatcmp($a, $b);
});
print_r($data);
// [
// 'item_1' => 'First',
// 'item_2' => 'Second',
// 'item_3' => 'Third',
// 'item_10' => 'Tenth',
// ]

Natural sorting sorts strings with numbers the way humans expect:

$files = ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt'];
// Regular sort
sort($files);
print_r($files);
// ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt'] ❌
// Natural sort
natsort($files);
print_r($files);
// ['file1.txt', 'file2.txt', 'file10.txt', 'file20.txt'] ✓

natcasesort() - Case-Insensitive Natural Order

Section titled “natcasesort() - Case-Insensitive Natural Order”
$files = ['File1.txt', 'file10.txt', 'File2.txt'];
natcasesort($files);
print_r($files);
// ['File1.txt', 'File2.txt', 'file10.txt']

Sort multiple arrays simultaneously, or one array by multiple columns:

// Sort one array by multiple criteria
$data = [
['name' => 'Alice', 'age' => 30, 'salary' => 50000],
['name' => 'Bob', 'age' => 25, 'salary' => 60000],
['name' => 'Charlie', 'age' => 30, 'salary' => 55000],
];
// Extract columns
$ages = array_column($data, 'age');
$salaries = array_column($data, 'salary');
// Sort by age (asc), then salary (desc)
array_multisort(
$ages, SORT_ASC,
$salaries, SORT_DESC,
$data
);
print_r($data);
// [
// ['name' => 'Bob', 'age' => 25, 'salary' => 60000],
// ['name' => 'Charlie', 'age' => 30, 'salary' => 55000],
// ['name' => 'Alice', 'age' => 30, 'salary' => 50000],
// ]
$names = ['Alice', 'Bob', 'Charlie'];
$scores = [85, 92, 78];
// Sort both arrays by scores (descending)
array_multisort($scores, SORT_DESC, $names);
print_r($names); // ['Bob', 'Alice', 'Charlie']
print_r($scores); // [92, 85, 78]
class Product
{
public function __construct(
public string $category,
public string $name,
public float $price
) {}
}
$products = [
new Product('Electronics', 'Laptop', 1200),
new Product('Books', 'PHP Guide', 40),
new Product('Electronics', 'Mouse', 25),
new Product('Books', 'Algorithms', 50),
];
// Sort by: category (asc), then price (desc), then name (asc)
usort($products, function($a, $b) {
// Compare category
$catCompare = $a->category <=> $b->category;
if ($catCompare !== 0) return $catCompare;
// Compare price (descending)
$priceCompare = $b->price <=> $a->price;
if ($priceCompare !== 0) return $priceCompare;
// Compare name
return $a->name <=> $b->name;
});
$data = [5, null, 3, null, 1, 4];
usort($data, function($a, $b) {
// Nulls at the end
if ($a === null && $b === null) return 0;
if ($a === null) return 1;
if ($b === null) return -1;
return $a <=> $b;
});
print_r($data); // [1, 3, 4, 5, null, null]
$words = ['Banana', 'apple', 'Cherry', 'date'];
usort($words, function($a, $b) {
return strcasecmp($a, $b);
});
print_r($words); // ['apple', 'Banana', 'Cherry', 'date']
$names = ['Ömer', 'Alice', 'Åsa', 'Bob'];
// Set locale
setlocale(LC_COLLATE, 'sv_SE.UTF-8');
usort($names, function($a, $b) {
return strcoll($a, $b); // Locale-aware comparison
});
// Inefficient: complex calculation in comparison
usort($items, function($a, $b) {
$scoreA = $this->calculateComplexScore($a); // Called many times!
$scoreB = $this->calculateComplexScore($b);
return $scoreB <=> $scoreA;
});
// Better: pre-calculate scores
$scores = array_map(fn($item) => $this->calculateComplexScore($item), $items);
array_multisort($scores, SORT_DESC, $items);
// Overkill for simple sorting
usort($numbers, fn($a, $b) => $a <=> $b);
// Better: use built-in
sort($numbers);
// Much faster for simple cases!
class SearchResult
{
public function __construct(
public string $title,
public float $relevanceScore,
public int $views,
public \DateTime $publishedAt
) {}
}
function sortSearchResults(array $results): array
{
usort($results, function($a, $b) {
// Primary: relevance score (desc)
$scoreCompare = $b->relevanceScore <=> $a->relevanceScore;
if ($scoreCompare !== 0) return $scoreCompare;
// Secondary: views (desc)
$viewsCompare = $b->views <=> $a->views;
if ($viewsCompare !== 0) return $viewsCompare;
// Tertiary: publish date (newest first)
return $b->publishedAt <=> $a->publishedAt;
});
return $results;
}
function sortProducts(array $products, string $sortBy): array
{
$comparators = [
'price_asc' => fn($a, $b) => $a['price'] <=> $b['price'],
'price_desc' => fn($a, $b) => $b['price'] <=> $a['price'],
'rating' => fn($a, $b) => $b['rating'] <=> $a['rating'],
'popularity' => fn($a, $b) => $b['sales'] <=> $a['sales'],
'newest' => fn($a, $b) => $b['created_at'] <=> $a['created_at'],
];
if (isset($comparators[$sortBy])) {
usort($products, $comparators[$sortBy]);
}
return $products;
}
function sortByFileSize(array $files): array
{
uasort($files, function($a, $b) {
$sizeA = filesize($a);
$sizeB = filesize($b);
return $sizeB <=> $sizeA; // Largest first
});
return $files;
}
// ✅ Good: Use built-in for simple sorting
sort($numbers);
// ❌ Bad: Unnecessary complexity
usort($numbers, fn($a, $b) => $a <=> $b);
// ❌ Bad: Expensive calculation in comparison
usort($items, function($a, $b) {
$scoreA = $this->calculateComplexScore($a); // Called n log n times!
$scoreB = $this->calculateComplexScore($b);
return $scoreB <=> $scoreA;
});
// ✅ Good: Calculate once
$scores = array_map(fn($item) => $this->calculateComplexScore($item), $items);
array_multisort($scores, SORT_DESC, $items);
// Performance: O(n) + O(n log n) vs O(n² log n)!
// Associative array - preserve keys
$ages = ['Alice' => 30, 'Bob' => 25, 'Charlie' => 35];
asort($ages); // ✅ Keys preserved
// Indexed array - reset keys OK
$numbers = [3, 1, 4, 1, 5];
sort($numbers); // ✅ Keys reset to 0,1,2...

4. Use Natural Sorting for Human-Friendly Data

Section titled “4. Use Natural Sorting for Human-Friendly Data”
$files = ['file1.txt', 'file10.txt', 'file2.txt'];
// ❌ Bad: String sort gives wrong order
sort($files); // ['file1.txt', 'file10.txt', 'file2.txt']
// ✅ Good: Natural sort
natsort($files); // ['file1.txt', 'file2.txt', 'file10.txt']
// Old way (verbose)
usort($items, function($a, $b) {
if ($a < $b) return -1;
if ($a > $b) return 1;
return 0;
});
// ✅ Modern way (clean)
usort($items, fn($a, $b) => $a <=> $b);

Pitfall 1: Forgetting In-Place Modification

Section titled “Pitfall 1: Forgetting In-Place Modification”
// ❌ Wrong: sort() returns bool, not sorted array
$sorted = sort($numbers);
print_r($sorted); // bool(true) - not what you want!
// ✅ Correct: sort() modifies in place
sort($numbers);
$sorted = $numbers; // Copy if needed

Pitfall 2: Using usort() When Built-in Works

Section titled “Pitfall 2: Using usort() When Built-in Works”
// ❌ Inefficient: 3-4x slower than built-in
usort($numbers, fn($a, $b) => $a <=> $b);
// ✅ Better: Use optimized built-in
sort($numbers);
// Performance impact on 10,000 elements:
// usort(): 15ms
// sort(): 3.5ms (4x faster!)
class Product {
public function getScore() {
return $this->calculateComplexScore(); // Expensive!
}
}
// ❌ Bad: O(n² log n) - score calculated millions of times!
usort($products, fn($a, $b) => $b->getScore() <=> $a->getScore());
// ✅ Good: O(n) pre-calculation + O(n log n) sort
$scores = array_map(fn($p) => $p->getScore(), $products);
array_multisort($scores, SORT_DESC, $products);
// PHP's sort() is NOT guaranteed to be stable
$data = [
['value' => 5, 'id' => 'a'],
['value' => 3, 'id' => 'b'],
['value' => 5, 'id' => 'c'],
];
usort($data, fn($a, $b) => $a['value'] <=> $b['value']);
// Order of 'a' and 'c' is unpredictable!
// ✅ Solution: Include secondary sort for stability
usort($data, function($a, $b) {
$cmp = $a['value'] <=> $b['value'];
return $cmp !== 0 ? $cmp : $a['id'] <=> $b['id'];
});
// Or use array_multisort for guaranteed stability
$values = array_column($data, 'value');
$ids = array_column($data, 'id');
array_multisort($values, SORT_ASC, $ids, SORT_ASC, $data);
// ❌ Problem: Sorting array of objects by reference
$objects = [&$obj1, &$obj2, &$obj3];
sort($objects); // May cause unexpected behavior
// ✅ Solution: Sort array of values, not references
$objects = [$obj1, $obj2, $obj3]; // No references
sort($objects);
$names = ['Åsa', 'Alice', 'Ömer', 'Bob'];
// ❌ Problem: Wrong order for non-English names
sort($names); // ASCII order, not linguistic order
// ✅ Solution: Use locale-aware comparison
setlocale(LC_COLLATE, 'sv_SE.UTF-8');
usort($names, fn($a, $b) => strcoll($a, $b));
// Correct Swedish alphabetical order
$versions = ['1.10', '1.2', '1.1'];
// ❌ Problem: String sort gives wrong order
sort($versions); // ['1.1', '1.10', '1.2'] ✗
// ✅ Solution 1: Use SORT_NUMERIC flag
sort($versions, SORT_NUMERIC); // ['1.1', '1.2', '1.10'] ✓
// ✅ Solution 2: Use version_compare()
usort($versions, fn($a, $b) => version_compare($a, $b));

Create a function that sorts an array of records by multiple fields with configurable order:

function multiSort(array $data, array $sortFields): array
{
// $sortFields = [
// ['field' => 'category', 'order' => 'asc'],
// ['field' => 'price', 'order' => 'desc'],
// ]
// Your code here
}

Sort tasks by priority, but group “urgent” tasks first regardless of priority number:

function sortTasks(array $tasks): array
{
// Sort: urgent first, then by priority, then by created date
// Your code here
}

Sort an array of version strings correctly:

function sortVersions(array $versions): array
{
// ['1.10.0', '1.2.0', '1.2.1'] → ['1.2.0', '1.2.1', '1.10.0']
// Your code here
}
  • Use built-in functions when possible—they’re optimized
  • sort() resets keys, asort() maintains keys
  • usort() for custom comparisons with comparison function
  • Spaceship operator (<=>) simplifies comparisons
  • array_multisort() for multi-column sorting
  • natsort() for natural (human-friendly) sorting
  • Sort flags control comparison behavior
  • Pre-calculate expensive values before sorting
  • Sorting is in-place—functions modify the array

Congratulations! You’ve completed the sorting algorithms section. In the next chapter, we’ll move on to Searching Algorithms, starting with Linear Search & Variants.

All code examples from this chapter are available in the GitHub repository:

View Chapter 10 Code Samples

Files included:

  • 01-basic-sorting-functions.php - Demonstrates sort(), rsort(), asort(), arsort(), ksort(), krsort(), and natsort()
  • 02-custom-sorting-usort.php - Custom comparators with usort(), uasort(), uksort() and best practices
  • 03-advanced-sorting-techniques.php - Advanced topics: Intl Collator, SplFixedArray, shuffle(), mixed types, performance optimization
  • README.md - Complete documentation and usage guide

Clone the repository to run the examples locally:

Terminal window
git clone https://github.com/dalehurley/codewithphp.git
cd codewithphp/code/php-algorithms/chapter-10
php 01-basic-sorting-functions.php

Continue to Chapter 11: Linear Search & Variants.