Sorting Numbers Correctly in JS Arrays Tutorial

Learn why JavaScript sorts numbers wrong by default and how to fix it. Covers numeric compare functions, ascending/descending order, sorting floats, handling NaN and Infinity, sorting mixed data, and performance tips.

JavaScriptbeginner
12 min read

JavaScript's sort() method converts every element to a string before comparing, which means [10, 9, 80] sorts to [10, 80, 9] by default. This is the single most common JavaScript sorting mistake, and it affects every developer who calls sort() on a numeric array without a compare function. This tutorial covers exactly why it happens, how to fix it, and every edge case you will encounter when sorting numbers in JavaScript.

Why Numbers Sort Wrong by Default

The default sort() behavior converts each element to a string, then compares those strings by Unicode code points:

javascriptjavascript
const prices = [100, 25, 3, 50, 10, 200];
 
prices.sort();
console.log(prices); // [10, 100, 200, 25, 3, 50]

The string comparisons happen character by character:

NumberAs StringFirst CharacterUnicode Code Point
100"100""1"49
25"25""2"50
3"3""3"51
50"50""5"53
10"10""1"49
200"200""2"50

Since "1" < "2" < "3" < "5", the numbers sort as strings: all numbers starting with "1" come first, then "2", then "3", then "5". This produces completely wrong numerical ordering.

Never Sort Numbers Without a Compare Function

Calling array.sort() on numbers without (a, b) => a - b is a bug in 100% of cases. There is no scenario where lexicographic ordering of integers or floats produces correct numeric results.

The Correct Way to Sort Numbers

Pass a compare function that subtracts one number from the other:

javascriptjavascript
const prices = [100, 25, 3, 50, 10, 200];
 
// Ascending order
prices.sort((a, b) => a - b);
console.log(prices); // [3, 10, 25, 50, 100, 200]
 
// Descending order
prices.sort((a, b) => b - a);
console.log(prices); // [200, 100, 50, 25, 10, 3]

How a - b Works

The compare function must return:

  • A negative number if a should come before b
  • Zero if they are equal
  • A positive number if b should come before a
javascriptjavascript
// When a=10, b=25: 10 - 25 = -15 (negative → 10 before 25) ✓
// When a=50, b=25: 50 - 25 = 25  (positive → 25 before 50) ✓
// When a=25, b=25: 25 - 25 = 0   (equal → keep original order) ✓
Compare ResultMeaningExample
a - b < 0a comes first10 - 25 = -15
a - b === 0Equal, keep order25 - 25 = 0
a - b > 0b comes first50 - 25 = 25

Sorting Floating-Point Numbers

The a - b pattern works identically for decimals:

javascriptjavascript
const measurements = [3.14, 2.718, 1.414, 0.577, 2.236];
 
measurements.sort((a, b) => a - b);
console.log(measurements); // [0.577, 1.414, 2.236, 2.718, 3.14]

Sorting Currency Values

javascriptjavascript
const transactions = [99.99, 0.50, 149.95, 10.00, 5.99, 249.99];
 
// Ascending
transactions.sort((a, b) => a - b);
console.log(transactions); // [0.5, 5.99, 10, 99.99, 149.95, 249.99]

Handling Special Number Values

NaN and Infinity

NaN breaks the compare function because any arithmetic with NaN returns NaN, and sort() treats NaN comparisons as equal to everything, producing unpredictable placement:

javascriptjavascript
const data = [5, NaN, 3, 1, NaN, 4];
 
// Unpredictable: NaN contaminates comparisons
data.sort((a, b) => a - b);
console.log(data); // Order of NaN is implementation-dependent

Filter out NaN before sorting:

javascriptjavascript
const data = [5, NaN, 3, 1, NaN, 4];
 
const clean = data.filter(n => !Number.isNaN(n));
clean.sort((a, b) => a - b);
console.log(clean); // [1, 3, 4, 5]

Or push NaN values to the end:

javascriptjavascript
const data = [5, NaN, 3, 1, NaN, 4];
 
data.sort((a, b) => {
  if (Number.isNaN(a)) return 1;  // a goes to end
  if (Number.isNaN(b)) return -1; // b goes to end
  return a - b;
});
console.log(data); // [1, 3, 4, 5, NaN, NaN]

Infinity Values

Infinity and -Infinity work correctly with a - b without special handling:

javascriptjavascript
const values = [100, Infinity, -Infinity, 0, 50];
 
values.sort((a, b) => a - b);
console.log(values); // [-Infinity, 0, 50, 100, Infinity]

Sorting Object Arrays by Numeric Property

The most common real-world scenario is sorting objects by a number field:

javascriptjavascript
const products = [
  { name: "Keyboard", price: 129.99, rating: 4.5 },
  { name: "Mouse", price: 49.99, rating: 4.8 },
  { name: "Monitor", price: 349.99, rating: 4.2 },
  { name: "Webcam", price: 79.99, rating: 3.9 },
  { name: "Headphones", price: 199.99, rating: 4.7 },
];
 
// By price ascending
products.sort((a, b) => a.price - b.price);
console.log(products.map(p => `${p.name}: $${p.price}`));
// ["Mouse: $49.99", "Webcam: $79.99", "Keyboard: $129.99", ...]
 
// By rating descending (highest first)
products.sort((a, b) => b.rating - a.rating);
console.log(products.map(p => `${p.name}: ${p.rating}`));
// ["Mouse: 4.8", "Headphones: 4.7", "Keyboard: 4.5", ...]

Multi-Key Numeric Sort

Sort by one numeric field, then break ties with another:

javascriptjavascript
const scores = [
  { name: "Alice", points: 95, time: 120 },
  { name: "Bob", points: 88, time: 95 },
  { name: "Carol", points: 95, time: 105 },
  { name: "Dave", points: 88, time: 110 },
];
 
// Sort by points descending, then by time ascending (faster is better)
scores.sort((a, b) => {
  if (b.points !== a.points) return b.points - a.points;
  return a.time - b.time;
});
 
console.log(scores.map(s => `${s.name}: ${s.points}pts, ${s.time}s`));
// ["Carol: 95pts, 105s", "Alice: 95pts, 120s", "Bob: 88pts, 95s", "Dave: 88pts, 110s"]

Sorting Without Mutation

Like all array sorting, sort() mutates the original. Copy first when needed:

javascriptjavascript
const original = [42, 7, 19, 3, 88];
 
// Spread + sort
const ascending = [...original].sort((a, b) => a - b);
 
// toSorted() (ES2023)
const descending = original.toSorted((a, b) => b - a);
 
console.log(original);   // [42, 7, 19, 3, 88] — unchanged
console.log(ascending);  // [3, 7, 19, 42, 88]
console.log(descending); // [88, 42, 19, 7, 3]

Sorting Numeric Strings

When your data contains numbers stored as strings (common with form inputs and CSV parsing), convert before comparing:

javascriptjavascript
const inputValues = ["100", "25", "3", "50", "10"];
 
// Bug: still sorts as strings
inputValues.sort();
console.log(inputValues); // ["10", "100", "25", "3", "50"]
 
// Fix: parse inside the compare function
inputValues.sort((a, b) => Number(a) - Number(b));
console.log(inputValues); // ["3", "10", "25", "50", "100"]

Mixed Numbers and Numeric Strings

javascriptjavascript
const mixed = [30, "5", 100, "20", 8, "15"];
 
mixed.sort((a, b) => Number(a) - Number(b));
console.log(mixed); // ["5", 8, "15", "20", 30, 100]

Performance Tips

The built-in sort() uses TimSort (O(n log n)) in most engines. The callback runs O(n log n) times, so keep it cheap:

javascriptjavascript
// Slow: parsing dates inside compare (runs n log n times)
events.sort((a, b) => new Date(a.date) - new Date(b.date));
 
// Fast: pre-compute timestamps, sort, then map back
const withTimestamps = events.map(e => ({
  ...e,
  _ts: new Date(e.date).getTime(),
}));
withTimestamps.sort((a, b) => a._ts - b._ts);
Array Sizesort() CallsTip
< 100~500Any compare function is fine
100 - 10,000~10K - 130KAvoid expensive operations in compare
10,000+130K+Pre-compute sort keys, use typed arrays
Typed Arrays Sort Faster

For pure numeric sorting of large datasets (50K+ elements), consider using Float64Array or Int32Array. Their sort() method uses native numeric comparison by default, skipping the compare function overhead entirely.

Common Mistakes

Using sort() without compare for numbers:

javascriptjavascript
// This is ALWAYS a bug for numeric arrays
[10, 2, 30].sort(); // [10, 2, 30] → wrong!
[10, 2, 30].sort((a, b) => a - b); // [2, 10, 30] → correct

Returning boolean instead of number:

javascriptjavascript
const nums = [3, 1, 2];
 
// Bug: true (1) and false (0) — no negative value possible
nums.sort((a, b) => a > b);
// May produce wrong results because 0 means "equal"
 
// Fix: subtraction returns negative, zero, or positive
nums.sort((a, b) => a - b);

Mutating state during sort:

javascriptjavascript
const data = [5, 3, 8, 1];
 
// Bug: modifying state while sorting
let comparisons = 0;
data.sort((a, b) => {
  comparisons++;
  data.push(0); // Never do this!
  return a - b;
});

Best Practices

  1. Always use (a, b) => a - b for numeric ascending sort. Make it a reflex.
  2. Copy before sorting when the original order matters. Use [...arr].sort() or toSorted().
  3. Filter NaN before sorting. NaN values produce unpredictable results with any compare function.
  4. Pre-compute expensive sort keys. If the compare function parses dates, computes string lengths, or does regex matching, extract those values first.
  5. Use forEach() or map() to inspect sorted results without re-sorting.
Rune AI

Rune AI

Key Insights

  • Default sort is string-based: never omit the compare function for numeric arrays
  • Use (a, b) => a - b for ascending: swap to b - a for descending
  • Filter NaN values first: NaN breaks comparison logic unpredictably
  • Copy before sorting: use spread or toSorted() to preserve the original array
  • Pre-compute sort keys: extract expensive values before sorting large datasets
RunePowered by Rune AI

Frequently Asked Questions

Why does JavaScript sort numbers as strings by default?

The ECMAScript specification defines the default sort as lexicographic (string-based) comparison. This was a design decision in the original specification that accommodates mixed-type arrays. Since all values can be converted to strings, string comparison provides a universal default, even though it produces wrong results for numeric arrays.

Is there a built-in method for numeric sort?

There is no separate numeric sort method for regular arrays. You must always pass `(a, b) => a - b`. However, typed arrays (Int32Array, Float64Array) do sort numerically by default because they only contain numbers.

How do I sort numbers in descending order?

Swap the subtraction: `(a, b) => b - a`. When `b` is larger than `a`, the result is positive, so `a` moves after `b`, producing descending order.

What happens if my array has both numbers and undefined?

`sort()` always moves `undefined` values to the end of the array, regardless of the compare function. The compare function is never called with `undefined` as an argument.

Can I sort a very large array of numbers efficiently?

For arrays over 50,000 elements, consider using typed arrays (Float64Array) which sort numerically by default and avoid the function call overhead of custom comparators. For general arrays, pre-compute sort keys to minimize work inside the comparator.

Conclusion

Numeric sorting in JavaScript requires exactly one thing: a compare function. The (a, b) => a - b pattern handles ascending integers, floats, currency values, and object properties. The default sort() behavior (lexicographic) is never correct for numbers. By combining numeric comparators with NaN filtering, pre-computed sort keys for expensive operations, and toSorted() for immutable patterns, you can sort any numeric dataset correctly and efficiently. The rule is simple: if it contains numbers, always pass a compare function to sort().