Since PHP 8.0 every PHP sort function is stable; what does that guarantee, and how would you rely on it to break leaderboard ties?
answer
- equal elements keep input order
- undefined before PHP 8.0
- ties fall back to original position
- rsort keeps ties, array_reverse flips them
- sort by the minor key first
basics
~20 sA stable sort keeps elements that compare equal in the order they had before the sort. PHP guarantees this for every sort function since 8.0, so sorting by a secondary key first and the primary key second yields a tie-broken order.
solid answer
~40 sStability means that when the comparison says two elements are equal, they come out in the same relative order they went in. PHP 8.0 made every sort function stable: `sort`, `asort`, `ksort` and their reverse twins, the `u*` callback forms and `array_multisort()`. Before 8.0 the order of ties was undefined. On a leaderboard you can rely on it in two ways: insert entries in the tie-break order (say, the order players finished) and then `arsort()` by score, or sort twice, first by the minor key and then by the major one. Note that `rsort()` keeps tied elements in input order, while `sort()` followed by `array_reverse()` flips them. Stability preserves the *input* order, so if that order came from an unordered query, the ties are still arbitrary.
code
php · 14 lines<?php
declare(strict_types=1);
// Entries appended in the order players finished
$scores = ['ana' => 820, 'cy' => 820, 'bo' => 950];
$ranked = $scores;
arsort($ranked);
echo implode(',', array_keys($ranked)), PHP_EOL; // bo,ana,cy
$flipped = $scores;
asort($flipped);
$flipped = array_reverse($flipped, true);
echo implode(',', array_keys($flipped)), PHP_EOL; // bo,cy,anago deeper
Recall that since PHP 8.0 equal elements keep their original order in every sort function.
Explain how ties fall back to the original position, and why rsort() and sort() plus array_reverse() differ for tied elements.
Decide whether ranking code may rely on stability plus input order or needs an explicit tie-breaker, and spot tests that passed on PHP 7 by luck of tie order.
Make ranking rules that users see explicit in one tested comparator, so correctness does not hinge on how an upstream query or cache happened to order its rows.
## What stable means A sort is **stable** if elements that compare as equal keep their original relative order. Nothing else is promised: elements that differ are ordered by the comparison as usual. Stability only matters when there are **ties**, which on a leaderboard means players with the same score. Since PHP 8.0, all of PHP's sorting functions are stable. The manual states it for each of them, and the 8.0 migration notes list it as a new feature: previously, the relative order of equal elements was undefined. ## How PHP implements it Before sorting, PHP records each element's original position. When the comparison (built-in or your callback) returns zero, the engine does not stop there: it falls back to comparing those original positions, and the element that came first stays first. This fallback also applies in the reverse functions, so: - `rsort()`, `arsort()` and `krsort()` sort descending **and** keep tied elements in input order. - `sort()` followed by `array_reverse()` also gives a descending list, but `array_reverse()` flips the ties too, so equal elements end up in **reverse** input order. That difference is invisible for plain integers and very visible for rows or keyed entries. ## Using stability to break ties There are three ways to get a deterministic leaderboard, and stability enables two of them. 1. **Order the input by the tie-breaker, then sort by the main key.** If entries are appended in the order players finished, `arsort($scores)` ranks by score and, for equal scores, keeps whoever finished first ahead. 2. **Sort twice: minor key first, major key second.** Sort by completion time ascending, then by score descending. The second sort keeps the time order among equal scores because it is stable. 3. **Put every key in one comparator.** `[$b['score'], $a['seconds']] <=> [$a['score'], $b['seconds']]` in a `uasort()` call does not depend on stability at all. | Approach | Relies on stability | Cost | Main risk | |---|---|---|---| | input order + one sort | yes | one sort | input order silently changes upstream | | two passes | yes | two sorts | someone reorders or removes a pass | | composite comparator | no | one sort, more work per comparison | comparator gets complex | In review, the composite comparator is the most explicit. The two-pass form is fine when the passes sit next to each other and a comment says why. ## Where stability does not help - **Undefined input order.** Stability preserves the order you give it. Rows fetched from a database query without an `ORDER BY` do not have a guaranteed order, so their ties stay arbitrary after a stable sort; it only makes them faithfully arbitrary. - **Comparators that never tie.** If your comparator already includes a unique final key such as the player id, no two elements compare equal and stability has nothing to do. - **Comparators that tie too much.** A comparator returning a float difference is cast to `int`, so values within 1 of each other become ties. Stability then keeps them in input order, which can hide the bug: the result looks plausible but is not sorted by the real value. - **Code shared with PHP 7.** Before 8.0, tied elements could come out in a different order, which is a classic source of tests that passed on one PHP version and failed on another. ## Testing code that depends on tie order - **Put deliberate ties in the fixture.** A test with all-distinct scores cannot catch a tie-order bug; include at least two equal scores whose expected order differs from sorted-by-name or sorted-by-id order. - **Assert the full order, keys included**, not just the first element or the count. - **Run the suite on the lowest PHP version you support.** If that is still a 7.x release, tie order there is not guaranteed and the test may pass or fail by chance. ## What to say in a review If ranking rules matter to users (a prize, a visible position), make the tie-break explicit in the comparator, or document that the code relies on stable sorting and on a specific input order. Implicit reliance on how an upstream list happened to be ordered is the pattern that breaks when someone adds pagination, caching or a new query.
- Does a stable sort make a leaderboard deterministic if the rows come from a query without ORDER BY?No. Stability keeps the relative order the rows arrived in, and an unordered query does not guarantee that order. Add the tie-breaker to the query's `ORDER BY` or, better, to the comparator itself so the ranking does not depend on upstream order.
- Why can two comparators that look equivalent give different tie orders on the same data?Because a stable sort keeps ties in input order, but only for pairs the comparator actually calls equal. If one comparator returns zero for two rows and another separates them by a hidden detail, such as float truncation or a different key, the resulting tie groups differ.
A stable sort is like re-shelving library books by author when several are already in date order: books by the same author stay in the date order they arrived in, because the librarian never swaps two books that the rule says are equal.
saying these in an interview costs you the question
- Stable means the output is identical whatever order the input had
- Only sort() is stable; usort() and array_multisort() are not
- rsort() reverses the relative order of tied elements
- PHP 7 already guaranteed stable sorting
- sort() plus array_reverse() equals rsort() in every case