PHP · Intermediate

Associative Arrays and Sorting in PHP

10 min readUpdated September 24, 2026Every example verified

In short: An associative array maps keys to values: ['name' => 'Kit', 'age' => 3]. Read with $a['name'], test with isset or array_key_exists, and loop with foreach ($a as $key => $value). sort orders values and renumbers keys, asort and ksort keep the key associations, and usort takes a comparison callback, usually written with the spaceship operator <=>. Since PHP 8.0 every sort is.

Keys that mean something

Any PHP array can use strings as keys, and when it does it works as a record or a lookup table. $nest = ['species' => 'wren', 'eggs' => 5] reads like a small object: $nest['eggs'] is 5, assigning to $nest['ringed'] adds a field, unset($nest['eggs']) removes one, and foreach ($nest as $key => $value) walks the fields in the order they were added. PHP keeps insertion order for every array, which is why a tally built in a loop prints in first-seen order.

Keys are normalised. The string "7" becomes the integer 7, a float key is truncated, true becomes 1 and null becomes "". Strings that do not look like integers, such as "07" or "7a", stay strings. This rarely matters until you use numeric identifiers as keys and then wonder why "7" and 7 hit the same slot.

Reading a key that does not exist gives null with a warning, so check first. isset($a['k']) is true when the key exists and the value is not null; array_key_exists('k', $a) is true whenever the key exists, even with a null value. The null-coalescing operator $a['k'] ?? 0 reads a key with a default and no warning, which makes the counting pattern a one-liner: $tally[$word] = ($tally[$word] ?? 0) + 1. Nesting gives you records inside lists: an array of ['name' => ..., 'price' => ...] arrays is the usual shape for a table of data, and array_column($rows, 'price') pulls one column out, or array_column($rows, 'price', 'name') builds a name-to-price lookup.

Sorting comes in families that differ in what they order and whether they keep keys. sort and rsort order by value and renumber the keys from 0, so they destroy an associative array's keys. asort and arsort order by value and keep the keys. ksort and krsort order by key. usort, uasort and uksort take a callback that receives two elements (or two keys) and returns a negative number, zero or a positive number; the u variants follow the same key rules as their plain counterparts. All of them sort in place and return true, so the sorted array is the variable you passed, not the return value.

The spaceship operator <=> returns -1, 0 or 1 and is the natural body of a comparison callback: fn($a, $b) => $a['price'] <=> $b['price']. Swap the operands to sort descending. To sort by several fields, compare arrays: [$a['price'], $a['name']] <=> [$b['price'], $b['name']] compares prices first and names only when prices tie. Since PHP 8.0 all sorting functions are stable, so elements that compare equal keep their original relative order, which makes multi-pass sorting predictable. Flags such as SORT_STRING, SORT_NUMERIC and SORT_NATURAL change how plain sort compares mixed or numbered strings.

Syntax

 PHP · syntax
$stock = ["walnut" => 12, "birch" => 40];
$stock["oak"] = 40;            // add or replace
unset($stock["walnut"]);       // remove
$n = $stock["oak"];            // read
$n = $stock["pine"] ?? 0;      // read with default, no warning
isset($stock["oak"]);  array_key_exists("oak", $stock);
foreach ($stock as $wood => $qty) { ... }
array_keys($stock);  array_values($stock);  array_column($rows, "price", "name");

sort($a);  rsort($a);          // by value, keys renumbered
asort($a); arsort($a);         // by value, keys kept
ksort($a); krsort($a);         // by key
usort($rows, fn($x, $y) => $x["price"] <=> $y["price"]);                      // custom, renumbered
uasort($a, fn($x, $y) => $y <=> $x);                                          // custom, keys kept
usort($rows, fn($x, $y) => [$y["price"], $x["name"]] <=> [$x["price"], $y["name"]]);  // several fields

Sorting functions modify the array in place and return true. The callback must return an int: use <=>, not subtraction.

Records and tallies

Bird sightings are counted into an associative array, then a nest record shows isset against array_key_exists.

 PHP
<?php
$visits = ["kestrel", "robin", "kestrel", "wren", "robin", "kestrel"];
$tally = [];
foreach ($visits as $bird) {
    $tally[$bird] = ($tally[$bird] ?? 0) + 1;
}
foreach ($tally as $bird => $count) {
    echo $bird, ": ", $count, "\n";
}
echo isset($tally["owl"]) ? "owl seen" : "no owl", "\n";

$nest = ["species" => "wren", "eggs" => 5, "ringed" => null];
var_dump(isset($nest["ringed"]), array_key_exists("ringed", $nest));
unset($nest["eggs"]);
echo implode(", ", array_keys($nest)), "\n";

Output

kestrel: 3
robin: 2
wren: 1
no owl
bool(false)
bool(true)
species, ringed

The ?? 0 supplies a starting count the first time a bird is seen, so no key needs to be created in advance, and the tally prints in first-seen order because PHP preserves insertion order. ringed exists but holds null, which is exactly the case where isset says false and array_key_exists says true. After unset, the remaining keys are listed in their original order.

sort, asort, arsort, ksort and krsort

The same timber stock sorted five ways; json_encode shows whether keys survived.

 PHP
<?php
$stock = ["walnut" => 12, "birch" => 40, "ash" => 25, "oak" => 40];

$copy = $stock;
sort($copy);
echo json_encode($copy), "\n";

asort($stock);
echo json_encode($stock), "\n";

arsort($stock);
echo json_encode($stock), "\n";

ksort($stock);
echo json_encode($stock), "\n";

krsort($stock);
echo implode(" > ", array_keys($stock)), "\n";

Output

[12,25,40,40]
{"walnut":12,"ash":25,"birch":40,"oak":40}
{"birch":40,"oak":40,"ash":25,"walnut":12}
{"ash":25,"birch":40,"oak":40,"walnut":12}
walnut > oak > birch > ash

sort throws the wood names away and leaves a plain list, which is why it is applied to a copy. asort keeps them and orders by quantity; birch and oak tie at 40 and stay in their original order because sorting is stable. arsort reverses the order of values, and again birch stays ahead of oak. ksort and krsort ignore the values and order alphabetically by key.

usort with one rule, then two

Nursery plants are sorted by price, then by price descending with name as the tie-breaker, and finally looked up by name.

 PHP
<?php
$plants = [
    ["name" => "Fern", "price" => 6.5, "stock" => 3],
    ["name" => "Aloe", "price" => 4.0, "stock" => 12],
    ["name" => "Ivy", "price" => 6.5, "stock" => 8],
    ["name" => "Basil", "price" => 2.5, "stock" => 0],
];

usort($plants, fn($a, $b) => $a["price"] <=> $b["price"]);
echo implode(", ", array_column($plants, "name")), "\n";

usort($plants, fn($a, $b) => [$b["price"], $a["name"]] <=> [$a["price"], $b["name"]]);
foreach ($plants as $p) {
    printf("%-6s %5.2f %3d\n", $p["name"], $p["price"], $p["stock"]);
}

$inStock = array_filter($plants, fn($p) => $p["stock"] > 0);
echo count($inStock), " in stock\n";
$stockByName = array_column($plants, "stock", "name");
echo "Ivy: ", $stockByName["Ivy"], "\n";

Output

Basil, Aloe, Fern, Ivy
Fern    6.50   3
Ivy     6.50   8
Aloe    4.00  12
Basil   2.50   0
3 in stock
Ivy: 8

The first sort orders ascending by price; Fern and Ivy tie and keep their original order. The second compares two-element arrays: prices are compared first with the operands swapped for descending order, and names, in normal order, decide ties, which puts Fern before Ivy. array_filter keeps the records with stock, and array_column with a third argument turns the list of records into a name-to-stock lookup.

Common mistakes

  • Using sort on an associative array

    Why it goes wrong: sort renumbers the keys from 0, so ['ash' => 25, 'oak' => 40] becomes [25, 40] and the names are gone.

    Fix: Use asort/arsort to order by value or ksort/krsort by key; they keep the associations.

     PHP · fix
    <?php
    $stock = ["oak" => 40, "ash" => 25];
    asort($stock);
    foreach ($stock as $wood => $qty) {
        echo "$wood $qty\n";
    }
  • Testing for a key with isset when the value may be null

    Why it goes wrong: isset($row['note']) is false for a note that exists but is null, so the code treats an intentionally empty field as missing.

    Fix: Use array_key_exists when null is a legitimate value; use isset or ?? when null should count as absent.

  • Returning a subtraction from a usort callback

    Why it goes wrong: The callback must return an int. fn($a, $b) => $a['price'] - $b['price'] returns a float for prices such as 6.5 and 6.0, and PHP casts 0.5 to 0, treating them as equal.

    Fix: Return $a['price'] <=> $b['price'], which is always -1, 0 or 1.

  • Using the return value of a sort function

    Why it goes wrong: $sorted = sort($a); stores true, because sorting happens in place and the function returns a boolean.

    Fix: Sort the variable and then use that variable; copy it first if you need the original order.

Sorting functions

FunctionOrders byKeysDirection
sort / rsortvaluerenumbered from 0ascending / descending
asort / arsortvaluekeptascending / descending
ksort / krsortkeykeptascending / descending
usortcallback on valuesrenumbered from 0as the callback says
uasortcallback on valueskeptas the callback says
uksortcallback on keyskeptas the callback says

Where you use this

Grouping is the pattern that appears in almost every data task: read records, then collect them under a key. $byRoom[$row['room']][] = $row['name'] builds a room-to-names map in one line, and $totals[$cat] = ($totals[$cat] ?? 0) + $amount sums per category. The output usually has to be ordered, and that is where choosing between ksort (alphabetical categories) and arsort (largest first) decides what the report looks like. For records with several fields, usort with an array comparison gives a full sort order in a single expression.

 PHP · in practice
$totals = [];
while (($line = fgets(STDIN)) !== false) {
    [$category, $amount] = explode(",", trim($line));
    $totals[$category] = ($totals[$category] ?? 0) + (float) $amount;
}
arsort($totals);
foreach ($totals as $category => $sum) {
    printf("%-10s %8.2f\n", $category, $sum);
}

Key points

  • ['key' => value] builds a record; insertion order is preserved.
  • "7" and 7 are the same key; "07" is not.
  • isset is false for null values; array_key_exists only cares about the key; ?? reads with a default.
  • $tally[$k] = ($tally[$k] ?? 0) + 1 counts; $groups[$k][] = $v groups.
  • sort renumbers keys; asort/arsort and ksort/krsort keep them.
  • usort takes a callback returning an int; use <=> and compare arrays for several fields.
  • Sorting is in place, returns true, and has been stable since PHP 8.0.

Try it yourself

Each input line holds a name and a score. The program collects them into $scores keyed by name and prints them in input order. Change it to print them from highest score to lowest, keeping the input order for equal scores.

Your program
<?php
$scores = [];
while (($line = fgets(STDIN)) !== false) {
    [$name, $score] = explode(" ", trim($line));
    $scores[$name] = (int) $score;
}
foreach ($scores as $name => $score) {
    echo $name, " ", $score, "\n";
}
Input the program receives: mira 42 ↵ leo 58 ↵ suki 42 ↵ dev 71
Expected output: dev 71 leo 58 mira 42 suki 42

Practise this

Open the PHP playground

Frequently asked questions

What is the difference between isset and array_key_exists in PHP?

isset($a['k']) returns true only when the key exists and its value is not null. array_key_exists('k', $a) returns true whenever the key exists, whatever the value. If null is a meaningful value in your data, use array_key_exists; otherwise isset, or the ?? operator, is shorter and also avoids warnings.

Does sort() keep the keys of a PHP array?

No. sort and rsort reindex the result from 0, which is fine for lists and destructive for associative arrays. asort, arsort, ksort, krsort, uasort and uksort all preserve the key-value pairs; usort reindexes like sort.

Is sorting in PHP stable?

Yes, since PHP 8.0. Elements that compare as equal keep their original relative order in every sorting function, including usort. Before 8.0 the order of equal elements was not guaranteed. Stability means you can sort by a secondary field first and a primary field second and get a correct combined order, although a single usort with an array comparison is clearer.

Progress is stored only in this browser.

How this page was checked. Every program on it was run with PHP 8.4 at build time by the publishing checks, and the output shown is what it printed. Running PHP inside the browser is not available yet, so the Run button is absent rather than pretending; copy the code and run it with PHP 8.4 locally.