0

Sorting algorithms — bubble, merge, quick sort — PHP usort() internals

Intermediate5 min read·eng-08-002
interviewcompare

Concept

Sorting algorithms — understanding the tradeoffs, not just the code. In practice, use PHP's built-in sort()/usort() (they use Timsort, an adaptive O(n log n) algorithm). Know sorting internals for interviews and for choosing the right approach.

Bubble SortO(n²) time, O(1) space. Compare adjacent pairs, swap if out of order. Repeat n times. Simple but slow — only useful as a teaching example.

Selection SortO(n²) time, O(1) space. Find the minimum, place it at position 0. Find the next minimum, place at position 1. Etc. Fewer swaps than bubble sort.

Insertion SortO(n²) worst, O(n) best (already sorted). Builds a sorted subarray by inserting each element in its correct position. Excellent for small arrays and nearly-sorted data. Used by Timsort for small chunks.

Merge SortO(n log n) guaranteed. Divide in half, sort each half recursively, merge. Stable (preserves order of equal elements). O(n) extra space for the merge step. Predictable performance.

Quick SortO(n log n) average, O(n²) worst (bad pivot). Pivot, partition into smaller/larger, recurse. In-place (no extra array). Fastest in practice for cache performance. PHP's sort uses Timsort (hybrid), not quicksort.

PHP's sort() family:

  • sort() / rsort(): Sort array values, re-index.
  • asort() / arsort(): Sort values, preserve keys.
  • ksort() / krsort(): Sort by keys.
  • usort(): Sort with a custom comparison function.
  • All use Timsort (hybrid merge + insertion sort). O(n log n).

Stability: A stable sort preserves the relative order of equal elements. Timsort (PHP) is stable. Quicksort is not stable.

Code Example

php
<?php
// Bubble sort — O(n²) — do NOT use in production
function bubbleSort(array $arr): array
{
    $n = count($arr);
    for ($i = 0; $i < $n - 1; $i++) {
        $swapped = false;
        for ($j = 0; $j < $n - $i - 1; $j++) {
            if ($arr[$j] > $arr[$j + 1]) {
                [$arr[$j], $arr[$j + 1]] = [$arr[$j + 1], $arr[$j]];
                $swapped = true;
            }
        }
        if (!$swapped) break; // already sorted optimization
    }
    return $arr;
}

// Merge sort — O(n log n), stable, O(n) space
function mergeSort(array $arr): array
{
    $n = count($arr);
    if ($n <= 1) return $arr;

    $mid   = intdiv($n, 2);
    $left  = mergeSort(array_slice($arr, 0, $mid));
    $right = mergeSort(array_slice($arr, $mid));

    return merge($left, $right);
}

function merge(array $left, array $right): array
{
    $result = [];
    $i = $j = 0;
    while ($i < count($left) && $j < count($right)) {
        if ($left[$i] <= $right[$j]) $result[] = $left[$i++];
        else                          $result[] = $right[$j++];
    }
    return array_merge($result, array_slice($left, $i), array_slice($right, $j));
}

// Quick sort — O(n log n) average, O(n²) worst
function quickSort(array $arr): array
{
    if (count($arr) <= 1) return $arr;

    $pivot    = $arr[0];
    $left     = array_filter(array_slice($arr, 1), fn($x) => $x <= $pivot);
    $right    = array_filter(array_slice($arr, 1), fn($x) => $x > $pivot);

    return array_merge(quickSort(array_values($left)), [$pivot], quickSort(array_values($right)));
}

// PHP built-in — use these in practice
$data = [5, 3, 8, 1, 9, 2];
sort($data);                                        // [1, 2, 3, 5, 8, 9]

$users = [['name' => 'Charlie', 'age' => 30], ['name' => 'Alice', 'age' => 25]];
usort($users, fn($a, $b) => $a['age'] <=> $b['age']); // sort by age ascending

// Multi-column sort (like SQL ORDER BY name ASC, age DESC)
usort($users, function ($a, $b) {
    return $a['name'] <=> $b['name'] ?: $b['age'] <=> $a['age'];
});

// usort internals: PHP's Timsort is O(n log n), stable, adaptive
// When array is already sorted or nearly sorted, Timsort approaches O(n)