Sorting has a trap, copying has a rule
Two array jobs appear in almost every feature, showing items in order and working on a copy without wrecking the original, and JavaScript has a sharp edge in each. Meeting both now is cheaper than meeting them in a bug report.
sort() compares items as text by default. The method was designed to work on arrays of anything, so with no instructions it converts every item to a string and orders them alphabetically. For numbers that is simply wrong: [10, 9, 80].sort() gives [10, 80, 9], because the text "10" sorts before "80", which sorts before "9".
The fix is a compare function, an arrow that tells sort how to order any two items. A negative result means a goes first, a positive result means b goes first, and zero means they tie.
| Goal | Compare function |
|---|---|
| numbers, smallest first | (a, b) => a - b |
| numbers, largest first | (a, b) => b - a |
| text, alphabetical | (a, b) => a.localeCompare(b) |
sort() also rearranges the array in place, so the original order is gone afterward. When you still need the original, copy first:
xs.slice(start, end)returns a new array holding that piece ofxs, which is the job Python did withxs[start:end]. Called with no arguments,xs.slice()copies the whole array.[...xs]is the spread syntax, where the three dots pour every item ofxsinto a fresh array literal.[...xs].sort(...)is the standard sorted-copy idiom you will read everywhere.
The text sort next to a numeric sort
Four operations on one array of numbers, arranged so the default sort and the corrected sort sit next to each other.
const nums = [10, 9, 80, 21]; // default sort compares as text: "10" < "21" < "80" < "9" console.log([...nums].sort()); // a compare function sorts by number console.log([...nums].sort((a, b) => a - b)); // slice copies a piece without touching the original console.log(nums.slice(0, 2)); console.log(nums);
Output
[ 10, 21, 80, 9 ] [ 9, 10, 21, 80 ] [ 10, 9 ] [ 10, 9, 80, 21 ]
The first line is the trap in action, with 9 stranded at the end because its text form starts with a later character than 1, 2, or 8. The second line fixes it with (a, b) => a - b. The third shows slice(0, 2) taking positions 0 and 1 and stopping before position 2, exactly as Python's slicing did.
The last line is the reassurance: nums is still in its original order, because both sorts ran on spread copies rather than on the array itself.
Sorting objects by a property
Real lists are arrays of objects (users, products, scores), and the compare function is where you name the property that decides the order:
const products = [ { name: "mouse", price: 25 }, { name: "monitor", price: 180 }, { name: "cable", price: 8 } ]; const byPrice = [...products].sort((a, b) => a.price - b.price); // cable, mouse, monitor
The pattern is always the same: spread-copy, then a.<property> - b.<property> for ascending numbers. For text properties use a.name.localeCompare(b.name), a string method that answers negative/zero/positive the way sort expects, alphabetically.
A sorted copy, highest first
Everything from this lesson in five lines: a descending sort, on a copy, with the original still intact at the end.
const scores = [72, 95, 88, 61]; const sorted = [...scores].sort((a, b) => b - a); console.log(sorted); console.log(`Top score: ${sorted[0]}`); console.log(scores);
Output
[ 95, 88, 72, 61 ] Top score: 95 [ 72, 95, 88, 61 ]
The copy comes first, written [...scores] here, though scores.slice() would do the same job. Reversing the subtraction to (a, b) => b - a is what turns the order around, and it means the highest score lands at position 0, so sorted[0] is the top score without any extra searching.
The third line of output is the proof that matters. scores still holds its original arrival order, which is usually what you want to keep, since a display order and a storage order are different concerns.
What the default sort() does to numbers
Calling [1, 5, 10].sort() with no compare function gives [1, 10, 5]. Every item is converted to a string first, and alphabetically "1" comes before "10", which comes before "5".
Character-by-character comparison explains it. "1" and "10" start with the same character, and the shorter string wins the tie, so "1" sorts first. "5" loses to both because the character 5 comes after the character 1. The array was never compared as numbers at all.
Passing (a, b) => a - b restores the ordering you expected, giving [1, 5, 10]. It is worth treating a bare .sort() on numeric data as a bug on sight, since the output often looks plausible enough on small arrays to slip through review.