Skip to content
JavaScript Basics: Sort Arrays Without Changing the Original

JavaScript Basics: Sort Arrays Without Changing the Original

October 17, 2026

Sorting a list should not accidentally rearrange a list another part of your program still needs. Use toSorted() to create a sorted array while keeping the original order.

These examples use built-in features and were tested on Linux with Node.js v24.19.0. No packages are needed. Assertions check the results before the program finishes.

Prepare a scratch directory

You need Bash, a text editor, Node.js on your PATH, and the mktemp, rm, and rmdir utilities. Keep using the same terminal. These commands stop the shell if setup fails. Save each named source file as UTF-8; .mjs selects ES modules.

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

1. Sort a copy of a list

Save this as sort-labels.mjs:

import assert from 'node:assert/strict';

const labels = ['pear', 'apple', 'orange'];
const sortedLabels = labels.toSorted();

assert.deepEqual(sortedLabels, ['apple', 'orange', 'pear']);
assert.deepEqual(labels, ['pear', 'apple', 'orange']);
assert.notEqual(sortedLabels, labels);
console.log(`Sorted: ${sortedLabels.join(', ')}`);
console.log(`Original: ${labels.join(', ')}`);
console.log(`Same array: ${sortedLabels === labels}`);

Run it:

node sort-labels.mjs

Output:

Sorted: apple, orange, pear
Original: pear, apple, orange
Same array: false

toSorted() returns a new array. Calling labels.sort() would rearrange labels itself. The default ordering compares string representations by UTF-16 code units. It works for these lowercase English labels, but it is not a language-aware dictionary ordering.

2. Give numbers a numeric comparator

Save this as sort-numbers.mjs:

import assert from 'node:assert/strict';

const scores = [20, 3, 100, 11];
const defaultOrder = scores.toSorted();
const ascending = scores.toSorted((left, right) => left - right);
const descending = scores.toSorted((left, right) => right - left);

assert.deepEqual(defaultOrder, [100, 11, 20, 3]);
assert.deepEqual(ascending, [3, 11, 20, 100]);
assert.deepEqual(descending, [100, 20, 11, 3]);
assert.deepEqual(scores, [20, 3, 100, 11]);
console.log(`Default: ${defaultOrder.join(', ')}`);
console.log(`Ascending: ${ascending.join(', ')}`);
console.log(`Descending: ${descending.join(', ')}`);
console.log(`Original: ${scores.join(', ')}`);

Run it:

node sort-numbers.mjs

Output:

Default: 100, 11, 20, 3
Ascending: 3, 11, 20, 100
Descending: 100, 20, 11, 3
Original: 20, 3, 100, 11

A comparator returns a negative number to place the left value first, a positive number to place it second, or zero for a tie. Subtraction gives numeric ordering for these finite numbers. Reversing the subtraction reverses the order. Avoid a boolean comparator such as left > right; it cannot represent all three cases. Validate your inputs if they might include NaN or non-numeric values.

3. Preserve the order of ties

Save this as sort-priorities.mjs:

import assert from 'node:assert/strict';

const tickets = [
  { title: 'Write guide', priority: 2 },
  { title: 'Fix login', priority: 1 },
  { title: 'Add examples', priority: 2 },
  { title: 'Restore search', priority: 1 },
];
const orderedTickets = tickets.toSorted(
  (left, right) => left.priority - right.priority,
);
const orderedTitles = orderedTickets.map(ticket => ticket.title);

assert.deepEqual(orderedTitles, [
  'Fix login', 'Restore search', 'Write guide', 'Add examples',
]);
assert.deepEqual(tickets.map(ticket => ticket.title), [
  'Write guide', 'Fix login', 'Add examples', 'Restore search',
]);
assert.equal(orderedTickets[0], tickets[1]);
console.log(`Priority order: ${orderedTitles.join(', ')}`);
console.log(`Original first: ${tickets[0].title}`);
console.log(`Same ticket object: ${orderedTickets[0] === tickets[1]}`);

Run it:

node sort-priorities.mjs

Output:

Priority order: Fix login, Restore search, Write guide, Add examples
Original first: Write guide
Same ticket object: true

Here, a smaller priority number goes first. When priorities match, the comparator returns zero. Stable sorting preserves the input order of those ties, so “Fix login” stays ahead of “Restore search”. Keep comparators consistent and free of changes to their inputs or outside state.

The copy is shallow: both arrays still refer to the same ticket objects. Editing an object through either array would be visible through the other. toSorted() protects the original array order; it does not clone nested data.

Clean up

In the same terminal, remove only the example files, return to your original directory, and remove the empty scratch directory:

rm -- sort-labels.mjs sort-numbers.mjs sort-priorities.mjs
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

Acknowledgements

This post was created with assistance from AI.

Last updated on