Array.prototype.sort() sortiert ein Array in-place und liefert dasselbe (mutierte) Array zurück. Ohne Argument vergleicht es alle Elemente als Strings — das führt bei Zahlen zu der berüchtigten Reihenfolge [1, 10, 2]. Mit einer Compare-Funktion (a, b) => zahl lässt sich beliebig sortieren: negativ = a vor b, positiv = b vor a, 0 = unverändert. Seit ES2019 ist sort garantiert stabil (Elemente mit gleichem Compare-Wert behalten ihre Reihenfolge). Mit ES2023 liefert toSorted() die immutable Variante, die das Original unangetastet lässt.
Signatur & Default-Verhalten
arr.sort(compareFn?: (a: T, b: T) => number): T[]Ohne compareFn werden alle Elemente in Strings konvertiert und lexikographisch sortiert. Das ist fast nie, was man will:
const zahlen = [10, 1, 100, 2, 25];
zahlen.sort();
console.log(zahlen);
// → [1, 10, 100, 2, 25] — als Strings sortiert: "1" < "10" < "100" < "2"
// Korrekt:
const zahlen2 = [10, 1, 100, 2, 25];
zahlen2.sort((a, b) => a - b);
console.log(zahlen2);
// → [1, 2, 10, 25, 100][ 1, 10, 100, 2, 25 ]
[ 1, 2, 10, 25, 100 ]Faustregel: fast immer eine compareFn übergeben. Default-Sort ist nur korrekt für Strings, die lexikographisch sortiert werden sollen.
Die Compare-Funktion (a, b) => number
Die Compare-Funktion entscheidet pro Paar:
- negativ: a kommt vor b
- positiv: b kommt vor a
- 0: Reihenfolge gleich (stabil seit ES2019)
// Aufsteigend
const aufst = [3, 1, 4, 1, 5, 9, 2, 6].sort((a, b) => a - b);
console.log(aufst);
// Absteigend
const abst = [3, 1, 4, 1, 5, 9, 2, 6].sort((a, b) => b - a);
console.log(abst);
// Nach String-Länge
const namen = ['Anna', 'Bo', 'Christopher', 'David'];
namen.sort((a, b) => a.length - b.length);
console.log(namen);
// Locale-bewusst (lexicographisch mit Sprache)
const wuerter = ['Äpfel', 'Apfel', 'Zebra', 'Banane'];
wuerter.sort((a, b) => a.localeCompare(b, 'de'));
console.log(wuerter);[ 1, 1, 2, 3, 4, 5, 6, 9 ]
[ 9, 6, 5, 4, 3, 2, 1, 1 ]
[ 'Bo', 'Anna', 'David', 'Christopher' ]
[ 'Apfel', 'Äpfel', 'Banane', 'Zebra' ]a - b (Subtraktion) ist das idiomatische Pattern für Numeric-Sort — kürzer als if (a < b) return -1; ....
Object-Sort nach Property
const users = [
{ name: 'Anna', alter: 30 },
{ name: 'Bob', alter: 25 },
{ name: 'Chris', alter: 40 },
];
// Nach Alter aufsteigend
users.sort((a, b) => a.alter - b.alter);
console.log(users);
// Nach Name alphabetisch
users.sort((a, b) => a.name.localeCompare(b.name));
console.log(users);[
{ name: 'Bob', alter: 25 },
{ name: 'Anna', alter: 30 },
{ name: 'Chris', alter: 40 }
]
[
{ name: 'Anna', alter: 30 },
{ name: 'Bob', alter: 25 },
{ name: 'Chris', alter: 40 }
]Mehrfach-Sort — mehrere Kriterien
Eine Compare-Funktion kann mehrere Kriterien kaskadieren — zuerst nach A sortieren, bei Gleichstand nach B.
const data = [
{ abt: 'IT', name: 'Anna' },
{ abt: 'HR', name: 'Chris' },
{ abt: 'IT', name: 'Bob' },
{ abt: 'HR', name: 'Anna' },
];
// Erst nach Abteilung, dann nach Name
data.sort((a, b) => {
const abt = a.abt.localeCompare(b.abt);
if (abt !== 0) return abt;
return a.name.localeCompare(b.name);
});
console.log(data);[
{ abt: 'HR', name: 'Anna' },
{ abt: 'HR', name: 'Chris' },
{ abt: 'IT', name: 'Anna' },
{ abt: 'IT', name: 'Bob' }
]Stable Sort (ES2019)
Seit ES2019 garantiert die Spec, dass sort stabil ist. „Stabil" heißt: Elemente, die nach der Compare-Funktion gleich sind, behalten ihre ursprüngliche Reihenfolge.
const items = [
{ id: 1, gruppe: 'A' },
{ id: 2, gruppe: 'B' },
{ id: 3, gruppe: 'A' },
{ id: 4, gruppe: 'B' },
{ id: 5, gruppe: 'A' },
];
items.sort((a, b) => a.gruppe.localeCompare(b.gruppe));
console.log(items);
// Ergebnis: alle 'A' vor allen 'B', und INNERHALB jeder Gruppe
// bleibt die Original-Reihenfolge: 1, 3, 5 für A; 2, 4 für B[
{ id: 1, gruppe: 'A' },
{ id: 3, gruppe: 'A' },
{ id: 5, gruppe: 'A' },
{ id: 2, gruppe: 'B' },
{ id: 4, gruppe: 'B' }
]Vor ES2019 war dieses Verhalten Engine-abhängig — V8 war instabil bei kleinen Arrays, andere Engines stabil. Heute überall verlässlich.
toSorted() — immutable (ES2023)
toSorted() macht semantisch dasselbe wie sort(), lässt aber das Original unangetastet und liefert ein neues Array.
const original = [3, 1, 4, 1, 5, 9, 2, 6];
const sortiert = original.toSorted((a, b) => a - b);
console.log(original); // [3, 1, 4, 1, 5, 9, 2, 6] — unverändert
console.log(sortiert); // [1, 1, 2, 3, 4, 5, 6, 9]
console.log(original === sortiert); // false[ 3, 1, 4, 1, 5, 9, 2, 6 ]
[ 1, 1, 2, 3, 4, 5, 6, 9 ]
falseBrowser-Support: Chrome 110+, Firefox 115+, Safari 16+, Node 20+ — Baseline 2024. Polyfill: arr.slice().sort(...).
Compare-Funktion-Fallen
Falle 1: a > b statt Number-Subtraktion. Liefert Boolean — wird zu 0/1 gecastet, nie negativ.
const x = [3, 1, 2].sort((a, b) => a > b); // Boolean!
console.log(x); // unspezifiziert, oft falsch
// Korrekt:
const y = [3, 1, 2].sort((a, b) => a - b);
console.log(y);[ 3, 1, 2 ]
[ 1, 2, 3 ]Falle 2: Floating-Point-Subtraktion bei sehr großen oder sehr kleinen Zahlen.
// Beide sind „groß" — Subtraktion kann Floats unsauber werden
const arr = [Number.MAX_VALUE, Number.MAX_VALUE - 1];
// Sicherer: explicit Vergleich
arr.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
console.log(arr[0] === Number.MAX_VALUE - 1);falseBei normalen Zahlen ist a - b sicher. Bei sehr großen Werten oder gemischten Floats lohnt der Vergleichs-Ausdruck.
Performance & Algorithmus
Die Spec schreibt keinen spezifischen Algorithmus vor — Engines wählen frei. V8 nutzt seit 2018 TimSort (eine adaptive Mischung aus Mergesort und Insertionsort) — stabil und im Durchschnitt schnell für realistische Daten. Komplexität: O(n log n) worst-case.
Bei sehr großen Arrays (>10.000) ist die Compare-Funktion oft der Flaschenhals — sie wird bis zu n log n mal aufgerufen. Optimierungen: einfache compareFn, möglichst billige Property-Reads, vorgemerkte Sort-Keys.
Häufige Stolperfallen
Default-Sort ist lexikographisch — Zahlen werden zu Strings
[10, 1, 2].sort() ergibt [1, 10, 2] — alle Elemente werden in Strings konvertiert. Bei Zahlen IMMER eine compareFn übergeben: (a, b) => a - b.
compareFn muss EINE Zahl zurückgeben, nicht Boolean
(a, b) => a > b liefert Boolean → wird zu 0 oder 1 gecastet, NIE negativ. Sort-Verhalten daher unspezifiziert. Korrekt: a - b oder explicit a < b ? -1 : a > b ? 1 : 0.
sort mutiert — in React/Redux nie direkt
setItems(items.sort(fn)) mutiert items UND gibt die gleiche Referenz zurück. React rendert nicht neu. Korrekt: setItems(items.toSorted(fn)) oder setItems([...items].sort(fn)).
Stable Sort garantiert seit ES2019
Vor ES2019 war V8 instabil bei kleinen Arrays. Heute überall stabil — Elemente mit gleichem Compare-Wert behalten Reihenfolge. Praktisch beim Multi-Key-Sort: nach geringerer Priorität zuerst sortieren, dann nach höherer.
localeCompare für sprachbewussten String-Sort
'Äpfel'.localeCompare('Apfel', 'de') liefert eine korrekte deutsche Sortierung. Default-String-Vergleich nutzt UTF-16 Codepoint-Order — 'Ä' (196) kommt nach 'z' (122). Bei Sprach-Daten daher fast immer localeCompare.
sort.reverse() vs. compareFn umkehren
arr.sort((a, b) => a - b).reverse() hat zwei Operationen + Mutation. arr.sort((a, b) => b - a) hat eine. Performance-marginal, semantisch klarer mit umgekehrter compareFn.
sort + Date: nach Date direkt subtrahieren möglich
arr.sort((a, b) => a.datum - b.datum) mit Date-Objects funktioniert dank valueOf-Conversion zu Millisekunden. Eleganter als String-Vergleich der ISO-Strings.
Schwartzian Transform: precompute sort keys für Performance
Wenn die compareFn teuer ist (z.B. Lowercase-Konvertierung), lohnt sich: erst arr.map(x => [computeKey(x), x]), dann sortieren, dann .map(([_, x]) => x). Reduziert wiederholte Computation in der compareFn.
Weiterführende Ressourcen
Externe Quellen
- Array.prototype.sort() – MDN
- Array.prototype.toSorted() – MDN
- String.prototype.localeCompare() – MDN
- Array.prototype.sort – ECMAScript Spec
- V8 Blog: Sort stability