How to Sort Arrays in JavaScript: Complete Guide

Master JavaScript array sorting with the sort() method. Covers default lexicographic behavior, custom compare functions, sorting objects by property, stable sort, locale-aware sorting, and common pitfalls.

JavaScriptbeginner
14 min read

The sort() method rearranges the elements of an array in place and returns the sorted array. By default, it converts every element to a string and sorts lexicographically (dictionary order). This default behavior is the source of JavaScript's most infamous sorting bug: numbers sort incorrectly without a custom compare function. This guide covers every aspect of sort(), from the default behavior to advanced patterns for sorting objects, dates, and locale-aware text.

Default Sort Behavior

Without a compare function, sort() converts elements to strings and orders them by Unicode code point:

javascriptjavascript
const fruits = ["banana", "cherry", "apple", "date"];
fruits.sort();
console.log(fruits); // ["apple", "banana", "cherry", "date"]
 
// Looks fine for strings. But watch numbers:
const numbers = [10, 9, 80, 1, 100, 21];
numbers.sort();
console.log(numbers); // [1, 10, 100, 21, 80, 9] — WRONG!

The numbers sort as strings: "1" < "10" < "100" < "21" < "80" < "9". This is correct lexicographic order but wrong numerical order.

Numbers Sort Wrong by Default

Calling sort() on a number array without a compare function produces wrong results. The numbers are converted to strings and sorted alphabetically. Always pass a compare function when sorting numbers.

The Compare Function

The compare function tells sort() how to order any two elements:

javascriptjavascript
array.sort(function (a, b) {
  // Return negative: a comes first
  // Return zero:     order unchanged
  // Return positive: b comes first
})
Return ValueMeaning
Negative numbera should come before b
0a and b are equal (keep original order)
Positive numberb should come before a

Sorting Numbers Correctly

javascriptjavascript
const numbers = [10, 9, 80, 1, 100, 21];
 
// Ascending
numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 9, 10, 21, 80, 100]
 
// Descending
numbers.sort((a, b) => b - a);
console.log(numbers); // [100, 80, 21, 10, 9, 1]

The a - b pattern works because it returns negative when a < b, zero when equal, and positive when a > b. For a deeper dive into numeric sorting patterns, see sorting numbers correctly.

Sorting Strings

Case-Sensitive (Default)

javascriptjavascript
const words = ["banana", "Apple", "cherry", "apricot"];
words.sort();
console.log(words); // ["Apple", "apricot", "banana", "cherry"]
// Uppercase letters sort before lowercase (A=65, a=97 in Unicode)

Case-Insensitive

javascriptjavascript
const words = ["banana", "Apple", "cherry", "apricot"];
 
words.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
console.log(words); // ["Apple", "apricot", "banana", "cherry"]

Locale-Aware Sorting with localeCompare

Different languages have different sorting rules. localeCompare() handles accented characters and language-specific ordering:

javascriptjavascript
const names = ["Zürich", "Aarhus", "Ångström", "Amsterdam"];
 
// Default sort: Unicode order (wrong for many languages)
console.log([...names].sort());
// ["Amsterdam", "Aarhus", "Zürich", "Ångström"]
 
// Locale-aware: respects language rules
console.log([...names].sort((a, b) => a.localeCompare(b, "en")));
// ["Aarhus", "Amsterdam", "Ångström", "Zürich"]
ApproachHandles AccentsHandles CasePerformance
Default sort()No (Unicode points)No (uppercase first)Fast
localeCompare()YesYes (configurable)Slower
Intl.CollatorYesYes (configurable)Fastest for repeated sorts

Using Intl.Collator for Performance

For sorting large arrays of strings, Intl.Collator creates a reusable comparator that is significantly faster than calling localeCompare() per element:

javascriptjavascript
const collator = new Intl.Collator("en", { sensitivity: "base" });
const cities = ["Zürich", "Aarhus", "Ångström", "Amsterdam", "Berlin"];
 
cities.sort(collator.compare);
console.log(cities); // ["Aarhus", "Amsterdam", "Ångström", "Berlin", "Zürich"]

Sorting Objects by Property

Sorting arrays of objects is the most common real-world use case. Extract the property and compare:

By String Property

javascriptjavascript
const employees = [
  { name: "Carol", department: "Engineering" },
  { name: "Alice", department: "Marketing" },
  { name: "Bob", department: "Engineering" },
];
 
employees.sort((a, b) => a.name.localeCompare(b.name));
console.log(employees.map(e => e.name)); // ["Alice", "Bob", "Carol"]

By Numeric Property

javascriptjavascript
const products = [
  { name: "Keyboard", price: 129 },
  { name: "Mouse", price: 49 },
  { name: "Monitor", price: 349 },
  { name: "Cable", price: 12 },
];
 
// Ascending by price
products.sort((a, b) => a.price - b.price);
console.log(products.map(p => `${p.name}: $${p.price}`));
// ["Cable: $12", "Mouse: $49", "Keyboard: $129", "Monitor: $349"]

By Date

javascriptjavascript
const events = [
  { title: "Launch", date: "2026-03-15" },
  { title: "Beta", date: "2026-01-10" },
  { title: "Alpha", date: "2025-11-20" },
  { title: "GA", date: "2026-06-01" },
];
 
events.sort((a, b) => new Date(a.date) - new Date(b.date));
console.log(events.map(e => e.title)); // ["Alpha", "Beta", "Launch", "GA"]

Multi-Key Sort (Primary + Secondary)

javascriptjavascript
const students = [
  { name: "Alice", grade: "A", gpa: 3.9 },
  { name: "Bob", grade: "B", gpa: 3.2 },
  { name: "Carol", grade: "A", gpa: 3.7 },
  { name: "Dave", grade: "B", gpa: 3.5 },
  { name: "Eve", grade: "A", gpa: 3.9 },
];
 
// Sort by grade (A first), then by GPA descending
students.sort((a, b) => {
  const gradeCompare = a.grade.localeCompare(b.grade);
  if (gradeCompare !== 0) return gradeCompare;
  return b.gpa - a.gpa; // Higher GPA first within same grade
});
 
console.log(students.map(s => `${s.name} (${s.grade}, ${s.gpa})`));
// ["Alice (A, 3.9)", "Eve (A, 3.9)", "Carol (A, 3.7)", "Dave (B, 3.5)", "Bob (B, 3.2)"]

sort() Mutates the Original Array

Unlike map() or filter(), sort() modifies the array in place:

javascriptjavascript
const original = [3, 1, 4, 1, 5, 9];
const sorted = original.sort((a, b) => a - b);
 
console.log(original); // [1, 1, 3, 4, 5, 9] — MODIFIED
console.log(sorted === original); // true — same reference

Sorting Without Mutation

To preserve the original array, copy it first using the spread operator or toSorted() (ES2023):

javascriptjavascript
const original = [3, 1, 4, 1, 5, 9];
 
// Option 1: spread + sort
const sorted1 = [...original].sort((a, b) => a - b);
 
// Option 2: toSorted() (ES2023) — non-mutating
const sorted2 = original.toSorted((a, b) => a - b);
 
console.log(original);  // [3, 1, 4, 1, 5, 9] — unchanged
console.log(sorted1);   // [1, 1, 3, 4, 5, 9]
console.log(sorted2);   // [1, 1, 3, 4, 5, 9]
toSorted() Is the Modern Alternative

ES2023 introduced toSorted(), which returns a new sorted array without modifying the original. It is the immutable counterpart to sort(), ideal for React state, Redux reducers, and any pattern where mutation is unwanted.

Stable Sort

JavaScript's sort() is guaranteed stable since ES2019. This means elements that compare as equal maintain their original relative order:

javascriptjavascript
const items = [
  { name: "Widget A", category: "tools" },
  { name: "Widget B", category: "tools" },
  { name: "Gadget A", category: "electronics" },
  { name: "Gadget B", category: "electronics" },
];
 
items.sort((a, b) => a.category.localeCompare(b.category));
 
// Stable: within "electronics", Gadget A still comes before Gadget B
console.log(items.map(i => i.name));
// ["Gadget A", "Gadget B", "Widget A", "Widget B"]

reverse() and toReversed()

To reverse an array's order (not sort in reverse):

javascriptjavascript
const letters = ["a", "b", "c", "d"];
 
// Mutating
letters.reverse();
console.log(letters); // ["d", "c", "b", "a"]
 
// Non-mutating (ES2023)
const original = ["a", "b", "c", "d"];
const reversed = original.toReversed();
console.log(original); // ["a", "b", "c", "d"] — unchanged
console.log(reversed); // ["d", "c", "b", "a"]

Common Mistakes

Sorting numbers without a compare function:

javascriptjavascript
const prices = [99, 5, 250, 10, 1000];
 
// Bug: lexicographic sort
prices.sort();
console.log(prices); // [10, 1000, 250, 5, 99]
 
// Fix: numeric compare
prices.sort((a, b) => a - b);
console.log(prices); // [5, 10, 99, 250, 1000]

Forgetting sort() mutates:

javascriptjavascript
const original = [5, 3, 8, 1];
 
// Bug: original is now sorted too
function getSorted(arr) {
  return arr.sort((a, b) => a - b);
}
const sorted = getSorted(original);
console.log(original); // [1, 3, 5, 8] — mutated!
 
// Fix: copy before sorting
function getSortedSafe(arr) {
  return [...arr].sort((a, b) => a - b);
}

Inconsistent compare functions:

javascriptjavascript
const items = [{ val: 3 }, { val: 1 }, { val: 2 }];
 
// Bug: returning only true/false instead of negative/zero/positive
items.sort((a, b) => a.val > b.val);
// Unreliable! true coerces to 1, false to 0 — no negative value
 
// Fix: proper numeric comparison
items.sort((a, b) => a.val - b.val);

Using sort() for shuffling:

javascriptjavascript
const arr = [1, 2, 3, 4, 5];
 
// Bug: biased shuffle
arr.sort(() => Math.random() - 0.5);
 
// Fix: Fisher-Yates shuffle
for (let i = arr.length - 1; i > 0; i--) {
  const j = Math.floor(Math.random() * (i + 1));
  [arr[i], arr[j]] = [arr[j], arr[i]];
}

Best Practices

  1. Always pass a compare function for numbers. Never rely on the default lexicographic sort for numeric data.
  2. Copy before sorting when you need to preserve the original. Use [...arr].sort() or toSorted().
  3. Use localeCompare() for strings when accent sensitivity or language-specific ordering matters.
  4. Use Intl.Collator for large datasets. It is significantly faster than localeCompare() for arrays with thousands of strings.
  5. Keep compare functions pure. They should only compare, not produce side effects.
Rune AI

Rune AI

Key Insights

  • Default sort is lexicographic: always pass a compare function for numbers
  • sort() mutates in place: use spread or toSorted() to preserve the original array
  • Compare function returns negative, zero, or positive: not true/false
  • Stable since ES2019: equal elements keep their original relative order
  • Use Intl.Collator for large string datasets: faster than repeated localeCompare() calls
RunePowered by Rune AI

Frequently Asked Questions

Does sort() modify the original array?

Yes. sort() sorts the array in place and returns a reference to the same array. If you need a new sorted array without modifying the original, use `[...arr].sort(compareFn)` or the ES2023 `toSorted()` method.

Why do numbers sort incorrectly without a compare function?

JavaScript's default sort converts every element to a string before comparing. The string "10" comes before "9" in lexicographic order because "1" (code point 49) is less than "9" (code point 57). Always pass `(a, b) => a - b` for numeric sorting.

Is JavaScript sort stable?

Yes, since ES2019. Stable sort means that elements which compare as equal retain their original relative order. All major browsers and Node.js implement stable sort.

What is the time complexity of sort()?

Most JavaScript engines use TimSort, which has O(n log n) average and worst-case time complexity. It is highly optimized for partially sorted data, achieving near O(n) for arrays that are already mostly sorted.

What is the difference between sort() and toSorted()?

sort() modifies the original array in place. toSorted() (ES2023) returns a new sorted array, leaving the original unchanged. Both accept the same optional compare function. Use toSorted() in immutable data patterns like React state management.

Conclusion

The sort() method is JavaScript's built-in tool for ordering array elements, but its default lexicographic behavior is a constant source of bugs for numeric data. The essential pattern is to always pass a compare function ((a, b) => a - b for numbers, localeCompare() for strings) and to copy the array before sorting when the original order must be preserved. With ES2023's toSorted() providing a non-mutating alternative, modern JavaScript now covers both the in-place and immutable sorting needs for every use case.