completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Sorting & copying safely

Sort arrays with reliable comparators, avoid accidental mutation, and use modern copying methods like toSorted, toReversed, toSpliced, and with.

By the end you can
  • 01
    Write comparators that workSort numbers, words, and objects with your own rule, so 100 never lands before 9 by surprise.
  • 02
    Know which methods change the arrayTell when sort, reverse, splice, and array[index] = value change the original array.
  • 03
    Sort a copy insteadUse toSorted, toReversed, toSpliced, and with when other code still needs the original array.

Put a list in order

Lists often come in the wrong order. Messages may come newest first. Prices may be mixed up. Names from a form come in the order people typed them.

To sort a list means to put its items in order. JavaScript arrays have methods that sort. Each time you sort, ask two questions:

  • What rule decides the order? For example, smallest number first.
  • Should the original array change? Or should you get a new, sorted array and keep the original as it was?

This lesson uses three things from earlier lessons:

  • From Comparisons: text is compared one character at a time, using each character's code. Capital letters come before small letters, so "Zebra" < "apple" is true. And "10" < "9" is true too, because 1 comes before 9.
  • From Objects & references: two variables can point at the same array. A change made through one variable shows up through the other.
  • From Arrow functions: short functions such as (a, b) => a - b.
The big idea

Give the sort your own rule when the default order is not what you want. Sort a copy when other code still needs the original order.

A first safe sortPop out in the code editor (opens in a new tab)JavaScript
const prices = [30, 10, 20];
const sortedPrices = prices.toSorted((a, b) => a - b);
console.log(sortedPrices.join(", "));

Line 1 makes a short list of prices. Line 2 calls toSorted. It makes a new array, sorts it, and returns it. The original array is not touched. The function (a, b) => a - b is the rule: smaller numbers first. Line 3 prints 10, 20, 30. The prices array still holds 30, 10, 20.

Real-life analogyDefault sort is like filing library cards by their characters

Picture a helper who files library cards by the characters printed on them. They look at the first character first. The card "100" starts with 1, so it goes next to "10", far away from "99".

In real life: Cards are compared one character at a time, from the left
In JavaScript: Default sort() turns items into text and compares the text
In real life: Card 100 goes before card 9, because 1 comes before 9
In JavaScript: [10, 9, 1, 100].sort() puts 100 before 9
In real life: You can give the librarian a different filing rule
In JavaScript: A comparator gives sort a different rule

The lesson goes in this order:

  1. The default sort rule, and why it surprises people with numbers.
  2. Your own rules, for numbers and for objects.
  3. Methods that change the original array: sort and reverse.
  4. Newer methods that return a changed copy instead: toSorted, toReversed, toSpliced, and with.
  5. Stable sorting, which keeps tied items in their first order.

sort() and comparators

STEP THROUGH

array.sort() puts the items of an array in order. It changes the original array: the items move around inside it.

If you call sort() with nothing in the brackets, it uses the default rule:

  • Each item is turned into text. The number 100 becomes the text "100".
  • The texts are compared one character at a time, from the left.
  • undefined items are not turned into text. They always go to the end.

This rule works for simple words. For numbers it is a trap: the text "100" starts with 1, so it comes before "9".

[10, 9, 1, 100].sort(). The top row, before, has the cards 10, 9, 1, and 100. An arrow marked compared as text goes down. The bottom row, after, has the cards 1, 10, 100, and 9. 100 comes before 9.[10, 9, 1, 100].sort()before1091100compared as textafter1101009100 comes before 9
With nothing in the brackets, sort() turns each number into text and compares the text one character at a time. "100" starts with 1, and 1 comes before 9, so 100 lands before 9.

Step through the code below to see this happen. Then pick another rule: smallest number first, largest number first, or one example with localeCompare.

localeCompare compares words the way people expect in a language, such as English. It has its own lesson in the next module, Comparing & sorting text. For now, remember one thing: for words that people read, the default rule is not the right tool.

Default sort vs a comparator
Step 0 of 3Ready
Your turn: follow the blue line

Guess the printed order, then step through the real run. Pick another rule and guess again.

Running in
  1. script
Next: line 1
const sorted = numbers.sort();console.log(sorted.join(", "));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Pick the rule that line 2 uses.

Each new rule starts the replay again. The localeCompare example is a preview of the Comparing & sorting text lesson in the next module.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

To use your own rule, pass a function to sort. This function is called a comparator. sort calls it with two items, a and b. The comparator returns a number:

  • A negative number (below 0) means a goes first.
  • A positive number (above 0) means b goes first.
  • 0 means a tie, so the two keep their order.
The comparator (a, b) => a - b returns a number. Negative: the order is a, b, so a goes first. Zero: the order a, b is kept. Positive: the order is b, a, so b goes first.(a, b) => a - bnegative0positivea, ba, bb, aa goes firstkeep the orderb goes first
The comparator only gives back a number. A negative number puts a first. A positive number puts b first. 0 means a tie, so the two keep their order.
Real-life analogyA comparator is a referee

The sort method does not know what order you want. So it asks a referee. The referee looks at two players at a time and answers one question: who goes first? The referee never moves the players. It only gives the answer.

In real life: The referee looks at two players
In JavaScript: The comparator gets two items, a and b
In real life: "Player A first"
In JavaScript: Return a negative number to put a before b
In real life: "Player B first"
In JavaScript: Return a positive number to put b before a
In real life: "It's a tie, stay where you are"
In JavaScript: Return 0 to keep their order

For numbers, the usual rule for smallest first is (a, b) => a - b. If a is smaller, a - b is negative, so a goes first. For largest first, swap the two: (a, b) => b - a.

For objects, compare the property you care about, such as a.age - b.age. To break a tie, add a second comparison after ||. The second comparison is used only when the first one gives 0. You can try this in the playground below.

A comparator always gives the same answerPop out in the code editor (opens in a new tab)JavaScript
const prices = [3, 1, 2];
const sorted = prices.toSorted((a, b) => a - b);

console.log(sorted.join(", "));
console.log(prices.join(", "));

A comparator must be consistent. That means the same two items must always give the same answer, every time sort asks. So inside a comparator, don't:

  • read values that can change while the sort runs,
  • use random numbers,
  • change the array.

This code prints 1, 2, 3 and then 3, 1, 2. toSorted sorted a copy, so prices kept its first order.

Comparator playground: sort people on a copy
The comparator in usePop out in the code editor (opens in a new tab)JavaScript
const sorted = [...people].sort((a, b) => {  return a.age - b.age;});
Sorted peopleasc
  1. Noah Patel, age 20
  2. Leo Patel, age 20
  3. Zoe Kim, age 25
  4. Mia Stone, age 30
  5. Ava Stone, age 30
First comparator calls in this browser (other browsers may differ)

The calls appear once the page has loaded in your browser.

Try it yourself
Sort by:Direction:

The code copies the array first with [...people], so the original list does not change. The comparator calls appear once the page has loaded in your browser.

sort calls the comparator many times. The browser picks which pairs to compare. Your comparator's job is to always return a negative number, a positive number, or zero.
Comparator bug: true and false are not enough

You may see (a, b) => a > b. It returns true or false. sort turns those into 1 and 0. So it never returns a negative number, and sort never hears "a goes first".

It may look right in one small test and then fail with a different list. Always return a number: negative, positive, or zero.

reverse() changes the original too

STEP THROUGH

reverse() flips the order of an array: the last item becomes the first. It is simpler than sort(), but it has the same risk:

  • It changes the original array.
  • It returns that same array, not a new one.

Changing the original array is called mutating it. That is fine for an array that only one small piece of code uses. It is risky for an array that other code also reads later, such as a list of products shared across your app.

sort and reverse return the same array
Step 0 of 7Ready
Your turn: follow the blue line

Step through the code. Watch two things: what prints, and whether a new array was made.

Running in
  1. script
Next: line 1
const sortedReturn = numbers.sort((a, b) => a - b);console.log(numbers.join(", "));console.log(numbers === sortedReturn);const reversedReturn = numbers.reverse();console.log(numbers.join(", "));console.log(numbers === reversedReturn);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Here is the bug this causes. Say you have two variables, visibleProducts and allProducts. Both point at the same array. You sort visibleProducts with sort(). Now allProducts is in the new order too, with no warning.

You saw this rule in Objects & references: a change made through one variable shows up through every variable that points at the same object. Arrays are objects, so they follow the same rule.

Copy first, then sort the copyPop out in the code editor (opens in a new tab)JavaScript
const prices = [30, 10, 20];const lowToHigh = [...prices].sort((a, b) => a - b);const highToLow = prices.slice().sort((a, b) => b - a); console.log(prices);    // [30, 10, 20]console.log(lowToHigh); // [10, 20, 30]

Line 2 copies the array with spread, [...prices]. Line 3 copies it with slice(). Each line then sorts its own copy, so prices keeps its order.

These are shallow copies. You get a new array, but the items inside are not copied. If the items are objects, both arrays hold the same objects.

A shallow copy shares the objects insidePop out in the code editor (opens in a new tab)JavaScript
const rows = [{ product: "Tea", price: 3 }];
const copy = [...rows];
copy[0].price = 4;

console.log(rows[0].price);
console.log(rows === copy);

Line 1 makes an array with one object in it, a row. Line 2 copies the array. Line 3 changes the price through the copy. The output is 4 and then false:

  • 4: the original row changed too, because both arrays hold the same row object.
  • false: the two arrays themselves are different arrays.

If you need to change the objects inside, copy those objects too.

toSorted, toReversed, toSpliced & with()

INTERACTIVE

Newer JavaScript has a copying partner for each method that changes the array. The copying partner returns a new, changed array. The original array stays as it was.

For example, sort() moves the items inside the original array. toSorted() makes a new array, sorts that, and returns it.

Left: prices.sort(...). The prices box now holds 10, 20, 30, so the original changed. Right: prices.toSorted(...). The prices box still holds 30, 10, 20, and an arrow leads to a new box, sorted, which holds 10, 20, 30.prices.sort(...)10, 20, 30pricesoriginal changedprices.toSorted(...)30, 10, 20pricesoriginal kept10, 20, 30sortednew array
sort() moves the items inside the original array, so prices changes. toSorted() leaves prices as it was and gives you a new, sorted array.

Each row of the table below is one change you might want. The middle column changes the original array. The last column gives you a changed copy instead.

Methods that change the original, and their copying partners
The change you wantChanges the originalReturns a changed copy
Sort the items.sort(compare)toSorted(compare)
Reverse the items.reverse()toReversed()
Remove or insert items.splice(start, deleteCount, ...items)toSpliced(start, deleteCount, ...items)
Replace the item at one index.array[index] = valuewith(index, value)
Does this browser have the copying methods?
Copying method examplesPop out in the code editor (opens in a new tab)JavaScript
const letters = ["A", "B", "C", "D"];console.log(letters.toReversed().join(""));console.log(letters.toSpliced(1, 2, "X").join(""));console.log(letters.with(1, "Z").join(""));console.log(letters.join(""));
Result

Checking which methods this browser has…

Try it yourself
Checking this browser

The check runs once the page has loaded in your browser.

This checks the browser you are using right now. The lesson's tests check the same results in Node.

with(index, value) is the copying partner of arr[i] = value. It returns a copy where the item at index is now value.

  • A negative index counts from the end, as it does with at(). So arr.with(-1, "done") replaces the last item.
  • An index outside the array throws a RangeError, so your code stops with an error right away. Writing arr[i] = value there would instead leave empty slots in the array, with no warning.
Does it change the original?
  • array.sort(compare)
  • array.reverse()
  • array.splice(1, 2)
  • array[1] = value
  • array.toSorted(compare)
  • array.toReversed()
  • array.toSpliced(1, 2)
  • array.with(1, value)
Try it yourself
0 of 8 correct

Put each line into a group: does it change the original array, or return a changed copy?

Choose a category for every card. You can change an answer at any time; Reset clears them all.
Which browsers have these methods

These copying methods were added to JavaScript in 2023, in the edition called ES2023. Modern browsers have them. If your site must also work in older browsers, you can:

  • check that a method exists before you use it,
  • add a polyfill: extra code that adds a missing method to old browsers,
  • or use the copy-then-sort pattern from the section above.

Stable sorting: ties keep their order

GUARANTEED

Two items are tied when the comparator returns 0 for them. A stable sort keeps tied items in the order they had before the sort.

In JavaScript, array sorting is always stable. This has been a rule of the language since the 2019 edition, ES2019.

Sort by age onlyPop out in the code editor (opens in a new tab)JavaScript
const queue = [  { name: "Mia", age: 30 },  { name: "Noah", age: 20 },  { name: "Ava", age: 30 },  { name: "Leo", age: 20 },];const byAge = queue.toSorted((a, b) => a.age - b.age);console.log(byAge.map((person) => person.name).join(", "));

Line 7 sorts a copy by age only. Line 8 prints the names: Noah, Leo, Mia, Ava.

  • Noah and Leo both have age 20. Noah came first in the list, so he stays before Leo.
  • Mia and Ava both have age 30. Mia came first, so she stays before Ava.
Sorting by age. The top row, before, is Mia 30, Noah 20, Ava 30, Leo 20. An arrow marked sort by age goes down. The bottom row, after, is Noah 20, Leo 20, Mia 30, Ava 30. Noah stays before Leo, and Mia stays before Ava.a.age - b.agebeforeMia 30Noah 20Ava 30Leo 20sort by ageafterNoah 20Leo 20Mia 30Ava 30ties keep their order
The comparator looks only at age, so Noah and Leo tie, and so do Mia and Ava. A stable sort keeps each tied pair in the order it had before: Noah before Leo, Mia before Ava.

A sort key is the property you sort by, such as age or name. Because sorting is stable, you can sort in two steps: first by the less important key, then by the main key. Ties on the main key keep the order from the first step.

One comparator with a tie-breaker is often easier to read, though: a.age - b.age || a.name.localeCompare(b.name).

Real-life analogyStable sorting is a fair queue

At a help desk, people with the same priority are served in the order they arrived. Nobody with the same priority jumps ahead. A stable sort treats tied items the same fair way.

In real life: People with the same priority are served in the order they arrived
In JavaScript: Items where the comparator returns 0 keep their order
In real life: A higher priority can move someone ahead
In JavaScript: A result that is not 0 can change the order
In real life: Everyone knows the queue rule ahead of time
In JavaScript: You can count on how a stable sort treats ties

Where you'll use this

Sorting and copying show up wherever the order on screen is different from the order the data was stored in. For example:

  • A shop page keeps the products in the order the server sent them, and shows them by price, lowest first.
  • A table lets people click Name, Age, or Joined date to sort, without changing the data that was loaded.
  • A shopping cart replaces one item and keeps the old cart, so the user can undo.
  • Sortable tables
  • Leaderboards
  • Search results
  • Shopping carts
  • Undo and redo
  • Reports in groups
A sort function that returns a new arrayPop out in the code editor (opens in a new tab)JavaScript
function sortRows(rows, sortKey) {  return rows.toSorted((a, b) => {    if (sortKey === "name") return a.name.localeCompare(b.name);    if (sortKey === "score") return b.score - a.score;    return 0;  });}

sortRows takes the table rows and a sort key:

  • Line 3 sorts by name.
  • Line 4 sorts by score, highest score first.
  • Line 5 returns 0 for any other key. Every pair is then a tie, so the rows keep their order.

Because it uses toSorted, sortRows returns a new array every time. The rows passed in never change. That makes it easy to test, and safe to use when you keep old versions for undo.

Sort the rows you show, keep the rows you loadedPop out in the code editor (opens in a new tab)JavaScript
const rows = [
  { product: "Tea", price: 3 },
  { product: "Bread", price: 2 },
];
const byPrice = rows.toSorted((a, b) => a.price - b.price);
console.log(byPrice.map((row) => row.product).join(", "));
console.log(rows.map((row) => row.product).join(", "));

The first line printed is Bread, Tea: the rows in price order, for the screen. The second is Tea, Bread: the rows in the order the server sent them. A sortable table needs exactly this. Make a sorted copy to show, and keep the loaded data as it was.

A small speed rule

Sorting compares items many times, so a longer list takes more work. For normal lists on a page, a clear comparator and one sort per click are fine. Don't sort again every time the page redraws if the list and the sort key have not changed.

Common misconceptions

"sort knows that numbers are numbers."

Not by default. With no comparator, sort compares the items as text, so 100 comes before 9.

"A comparator can return true or false."

A comparator must return a number: negative, positive, or zero. true and false become 1 and 0, so there is never a negative answer, and the order can come out wrong.

"sort and reverse give me a new array."

They change the original array and return that same array. Use toSorted or toReversed, or copy the array first.

"Stable means the original array doesn't change."

Stable only means tied items keep their order. sort is stable and changes the original. toSorted is stable and returns a copy.

"undefined is sorted like the word undefined."

sort always puts undefined items after all the other items. They are not turned into the text "undefined". When you print the array with join, they show up as empty spots.

undefined items go to the endPop out in the code editor (opens in a new tab)JavaScript
const tasks = ["email", undefined, "call"];
tasks.sort();
console.log(tasks.join(" | "));

Line 2 sorts the tasks. The two texts come first, in order: call, then email. The undefined task goes last. Line 3 joins the items with " | " between them, and join shows undefined as nothing. So the output is call | email | .

If you want a missing value to show a word, such as "none", change it to that word before you join or show the list.

Practice: sorting safely

5 EXERCISES
Exercise 1 · Warm-upSort numbers from smallest to largest

Run the code and check what it prints. Then replace toSorted with sort. Does the printed line change? Does scores change?

Starter codePop out in the code editor (opens in a new tab)JavaScript
const scores = [10, 9, 1, 100];
console.log(scores.toSorted((a, b) => a - b).join(", "));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeSort people by last name, then first name

    Sort the full names by last name. When two last names are the same, sort those two by first name. Use one comparator with a tie-breaker.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const people = ["Grace Hopper", "Ada Lovelace", "Grace Murray", "Ada Byron"];
    const sorted = people.toSorted((a, b) => {
      const [aFirst, aLast] = a.split(" ");
      const [bFirst, bLast] = b.split(" ");
      return aLast.localeCompare(bLast) || aFirst.localeCompare(bFirst);
    });
    console.log(sorted.join(" | "));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeKeep the original list in its order

      Other code still needs this list in its first order. Use a method that returns a sorted copy, so the first line printed still shows the original order.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const original = ["draft", "sent", "archived"];
      const sorted = original.toSorted();
      console.log(original.join(", "));
      console.log(sorted.join(", "));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeReplace one cart item without changing the cart

        Replace sugar with honey. The original cart must not change.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const cart = ["tea", "sugar", "milk"];
        const nextCart = cart.with(1, "honey");
        console.log(nextCart.join(", "));
        console.log(cart.join(", "));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengePredict the default sort

          Before you run it, write down exactly what the default sort will print.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          console.log([10, 9, 1].sort().join(", "));

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS

            Read each explanation, even when you got the answer right. Sorting mistakes often look correct on short lists.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What does sorting these numbers with no comparator print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log([10, 9, 1, 100].sort().join(", "));

              Choose an answer to see the explanation.

            2. Question 2 of 7Which comparator sorts numbers from smallest to largest?

              Choose an answer to see the explanation.

            3. Question 3 of 7Is the array returned by sort the same array?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const a = [3, 1, 2];
              const b = a.sort((x, y) => x - y);
              console.log(a === b);

              Choose an answer to see the explanation.

            4. Question 4 of 7Which method returns a copy with one item replaced?

              Choose an answer to see the explanation.

            5. Question 5 of 7What do these two lines print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const names = ["Mia", "Noah", "Ava"];
              console.log(names.toReversed().join(", "));
              console.log(names.join(", "));

              Choose an answer to see the explanation.

            6. Question 6 of 7What does stable sorting promise?

              Choose an answer to see the explanation.

            7. Question 7 of 7What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const values = [3, undefined, 1];
              values.sort();
              console.log(values.join("|"));

              Choose an answer to see the explanation.

            Key takeaways

            • Default sort() compares items as text. To sort numbers, give it a comparator.
            • A comparator returns a negative number to put a first, a positive number to put b first, and 0 for a tie.
            • sort, reverse, splice, and array[index] = value change the original array.
            • toSorted, toReversed, toSpliced, and with return a changed copy and leave the original alone.
            • JavaScript sorting is stable: tied items keep the order they had before.

            Remember the one-liner.
            Sort with a clear rule, and sort a copy when other code shares the array.

            Up next: Iterating arrays & array-likes.

            CompleteFrontend Clear concepts. Working examples.