Practical IT Training Live Projects Placement Support
Groot Academy
JavaScript Programming

JavaScript sort(): Fix Numeric Order Without Losing Track of Mutation

Choose numeric ordering and test whether sorting mutates or preserves the original array.

By Groot Academy November 1, 2026 7 min read

If a list puts 100 before 21, inspect the sorting rule before changing the data. JavaScript's ordinary Array sort method does not automatically choose numeric magnitude. A second question matters too: should sorting change the array that another part of your program already uses?

This lesson separates those questions with a small fixture and a runnable companion. It focuses on dense arrays containing finite Number values, not every possible JavaScript value.

Reproduce the surprising order

Run this example in a JavaScript console:

const numbers = [100, 21, 3, 1];
console.log(JSON.stringify(numbers.sort()));
// [1,100,21,3]

Without a comparator, these numeric elements are ordered using their string forms and UTF-16 code-unit comparison. The "1" at the start of "100" comes before the "2" at the start of "21". The array still contains Number values: a different ordering rule has not converted its stored elements into strings.

This is a useful diagnostic case because the expected numeric order is visibly different. An input such as [1, 2, 3] would hide the mistake by producing the same displayed order either way.

Each row in the diagram starts with a fresh copy. It compares two independent experiments, not intermediate states of one algorithm.

Use a signed numeric comparator

For ordinary finite Number values, use subtraction:

const ascending = [100, 21, 3, 1].sort((a, b) => a - b);
const descending = [100, 21, 3, 1].sort((a, b) => b - a);
console.log(JSON.stringify(ascending));  // [1,3,21,100]
console.log(JSON.stringify(descending)); // [100,21,3,1]

A negative comparator result places a before b, a positive result places it after b, and zero treats them as tied for ordering. The sign matters; returning exactly -1 or 1 is not required.

ComparisonAscending resultMeaning
3 minus 21Negative3 belongs before 21
21 minus 3Positive21 belongs after 3
3 minus 3ZeroEqual ordering value

Do not replace the comparator with (a, b) => a > b. A boolean becomes zero or one, so that function cannot supply the negative result needed when a should precede b. Do not rely on one engine's observed output from such an inconsistent comparison as a correctness guarantee.

Keep the comparator free of changes to the input or unrelated state. Sorting code should not depend on the order or number of comparator calls.

Check whether the array changed

sort changes the array on which it is called and returns that same array:

const original = [3, 1, 2];
const result = original.sort((a, b) => a - b);
console.log(JSON.stringify(original)); // [1,2,3]
console.log(result === original);      // true

Assigning result to another variable does not create a snapshot. An existing alias to original observes the reordered elements too.

Before fixing a screen that unexpectedly changes another list, write down which code owns the array. Decide whether mutation is intentional instead of assuming that returning a value means a method returned a copy.

Copy when the original order must survive

Where available, toSorted returns a new sorted array. It still needs a numeric comparator for numeric order:

const input = [3, 1, 2];
const sortedCopy = input.toSorted((a, b) => a - b);
console.log(JSON.stringify(sortedCopy)); // [1,2,3]
console.log(JSON.stringify(input));      // [3,1,2]

Check your target runtime rather than assuming every browser or Node installation provides the method. This lesson was executed on Node v24.19.0; that is a tested environment, not a minimum-version or universal-support claim.

For the dense Number arrays used here, copying before sort is another option:

const source = [3, 1, 2];
const copiedResult = [...source].sort((a, b) => a - b);
console.log(JSON.stringify(copiedResult)); // [1,2,3]
console.log(JSON.stringify(source));       // [3,1,2]

This fallback is not a promise that spread-plus-sort reproduces every detail of toSorted for arbitrary sparse arrays, subclasses or unusual objects. Both examples are deliberately bounded to the lesson's input model.

An outer-array copy also does not clone objects stored inside it. If two arrays contain references to the same records, editing a record is a separate mutation problem even when the outer order is independent.

Set an input policy before handling edge cases

The subtraction comparator is not input validation. Numeric strings, missing elements, NaN and non-finite values need a stated policy. The companion rejects those inputs rather than quietly converting or guessing.

Its sortFiniteNumbers helper first confirms an Array, then inspects every iterated value with Number.isFinite. Iterating a hole yields undefined in this ordinary-array example, so a sparse fixture is rejected too. It then chooses toSorted when available, otherwise copies before sorting.

Negative values, duplicates and an empty array are accepted. BigInt and mixed numeric representations are outside this helper's contract. If an application requires them, design and test a separate comparison policy.

Avoid fixing an invalid row by coercing everything with Number without considering what blank text or unexpected values should mean.

Run the companion and compare the output

Save sort-demo.cjs and run:

node sort-demo.cjs

The full companion deliberately requires toSorted support so it can test both modern copying behavior and the explicit spread-copy alternative. Its validation helper has a fallback, but that does not remove the full demonstration's runtime requirement.

Expected output:

default: [1,100,21,3]
ascending: [1,3,21,100]
descending: [100,21,3,1]
sort returns source: true
toSorted: [1,2,3]
source preserved: [3,1,2]
PASS: 18 checks

The checks include default order, ascending and descending results, aliases, preserved inputs, rejected invalid values, duplicates, negatives and shallow reference sharing. They use explicit assertions that throw on failure. A passing run supports this fixture; it is not a performance benchmark or proof of every application's input assumptions.

Practise with a small debugging note

Change the fixture to [8, 40, 2, 11]. Predict the default and numeric results before running it. Then create an alias to the input and record what it sees after sort. Repeat with a copied array and explain the difference.

For browser fundamentals around this exercise, use the HTML, CSS and JavaScript guide. For documenting expected outcomes and reproducible failures, continue with software testing basics.

Sources

References checked 17 October 2026: