Skip to main content

Sorting an array of numbers

To sort numbers in ascending order, call sort() with a compare function: arr.sort((a, b) => a - b). Without a comparator the method converts elements to strings and lines them up alphabetically, which puts 10 before 3, and that is the single most common mistake in interviews.

Theory

TL;DR

  • Ascending: arr.sort((a, b) => a - b).
  • Descending: arr.sort((a, b) => b - a).
  • With no comparator, sort() compares the string form of the elements.
  • The comparator returns a negative number, a positive number or zero, and that defines the order.
  • sort() mutates the source array and returns a reference to it.
  • Non mutating options: [...arr].sort(cmp) or arr.toSorted(cmp) (ES2023).

Quick example

javascript
const numbers = [10, 1, 3, 20]; numbers.sort((a, b) => a - b); console.log(numbers); // [1, 3, 10, 20]

The beginner mistake: sort() with no comparator

javascript
const numbers = [10, 1, 3, 20]; numbers.sort(); console.log(numbers); // [1, 10, 20, 3], string sorting

By default sort() converts elements to strings and orders them lexicographically, character by character using UTF-16 code units. That is why "10" comes before "2": the first character "1" is smaller than "2", and the remaining digits never get looked at.

One detail worth knowing: the array itself does not become an array of strings, the numbers stay numbers. Only the comparison keys are stringified, so the console shows numbers that are simply arranged "alphabetically".

The correct way: a compare function

javascript
const numbers = [10, 1, 3, 20]; numbers.sort((a, b) => a - b); console.log(numbers); // [1, 3, 10, 20]

The (a, b) => a - b function tells the engine how to compare numbers:

  • if a - b is less than 0, a goes first;
  • if a - b is greater than 0, b goes first;
  • if a - b equals 0, the order stays as it was.
Comparator valueWhat it means
Negativeput a before b
Positiveput b before a
Zerothe elements are equal, the order is preserved

Since ES2019 sort() is stable: elements for which the comparator returned zero keep their original relative order. That lets you sort in several passes, for example by name first and then by age.

Descending order and sorting without mutation

Descending order is the same comparator the other way round:

javascript
const numbers = [1, 3, 10, 20]; numbers.sort((a, b) => b - a); console.log(numbers); // [20, 10, 3, 1]

If the original array must stay intact, sort a copy:

javascript
const arr = [5, 2, 9, 1]; const sorted = [...arr].sort((a, b) => a - b); console.log(sorted); // [1, 2, 5, 9] console.log(arr); // [5, 2, 9, 1], the original is untouched

Modern engines ship a ready made non mutating method:

javascript
const sorted = arr.toSorted((a, b) => a - b); // ES2023, returns a new array

This matters a lot in React and similar libraries: state.items.sort(...) changes the same object, the reference does not change, and no re-render happens.

Neighbouring cases: strings and objects

The same principle covers any data, only the comparator changes:

javascript
// numbers as strings: convert before comparing ['10', '9', '2'].sort((a, b) => Number(a) - Number(b)); // ['2', '9', '10'] // text with correct alphabetical order ['apple', 'Banana', 'cherry'].sort((a, b) => a.localeCompare(b, 'en')); // objects by field const users = [{ name: 'Bob', age: 31 }, { name: 'Alice', age: 25 }]; users.sort((a, b) => a.age - b.age); // Alice first

For huge values that do not fit into Number, subtraction either returns a BigInt or loses precision, so there you write an explicit comparison:

javascript
arr.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));

In brief

TaskCodeResult
Ascendingarr.sort((a, b) => a - b)[1, 2, 3, 4]
Descendingarr.sort((a, b) => b - a)[4, 3, 2, 1]
Without changing the source array[...arr].sort(...)a new sorted array
Without changing it, modern syntaxarr.toSorted(...)a new sorted array

Summary: to sort numbers in ascending order, always pass a compare function: arr.sort((a, b) => a - b).

Common mistakes

  • Calling sort() with no arguments on numbers. That is lexicographic sorting, and on any array mixing one digit and two digit numbers the result is almost always wrong.
  • Returning true or false from the comparator. For example arr.sort((a, b) => a > b): a boolean coerces to 1 or 0, the engine never receives "less than", and the order becomes unpredictable. A comparator must return a signed number.
  • Forgetting that sort() mutates. It changes the source array and returns that same array, not a copy. For data you do not own, props and state, copy first.
  • Sorting an array that contains NaN. Any subtraction with NaN yields NaN, so the comparator effectively answers "unknown" and the order becomes garbage. Filter such values out beforehand.
  • Ignoring undefined and holes. undefined values always end up at the end, empty slots of a sparse array go even further, and the comparator is never called for them.
  • Comparing text with a - b or a > b. For strings with non ASCII letters, the correct order comes from localeCompare with the right locale.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.