cf.completefrontendCode editorOpen lab
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 reliable comparatorsSort numbers, strings, and objects without default-sort surprises.
  • 02
    Choose mutation or copyingKnow when sort, reverse, splice, and index assignment change the original array.
  • 03
    Use ES2023 copy methodsReach for toSorted, toReversed, toSpliced, and with when shared data must stay safe.

Sort without surprises

Arrays often arrive in the wrong order: newest messages first, product prices mixed together, or names from a form exactly as people typed them. JavaScript gives you powerful tools to reorder those lists, but two details matter every time: what rule decides the order and whether the original array changes.

This lesson sits after Array basics, Slicing, splicing & joining, Searching arrays, and map, filter & reduce. It also leans on Comparisons for the idea that strings compare by UTF-16 code unit, so "Zebra" < "apple" and "10" < "9". From Objects & references, remember that mutation is visible through every reference to the same array. Arrow functions give us compact comparators such as (a, b) => a - b.

The big idea

Use a comparator when the default string ordering is not exactly what you want. Use copying methods when another part of your app may still need the original order.

Real-life analogyDefault sort is alphabetizing library cards

Imagine a volunteer alphabetizing cards by the characters printed on them. The card “100” goes near “10”, not near “99”, because the first character decides early. That is exactly the surprise behind default array sorting.

In real life: Cards are compared letter by letter
In JavaScript: Default sort() compares string forms
In real life: Card 100 files before card 9 because it starts with 1
In JavaScript: [10, 9, 1, 100].sort() places 100 before 9
In real life: A librarian can be given a different filing rule
In JavaScript: A comparator supplies the rule JavaScript should use

Where the analogy stops: Library cards are always text. JavaScript arrays can hold numbers, objects, undefined values, and holes, so professional code should state the intended rule instead of hoping the default matches.

We will move from the default rule, to numeric and object comparators, to mutation, to the ES2023 copying methods: toSorted, toReversed, toSpliced, and with. Then we will use stable sorting to build multi-step sorts without losing tie order.

sort & comparators

STEP THROUGH

array.sort() sorts the array in place. If you do not pass a comparator, JavaScript converts the defined elements to strings and compares those strings. That is useful for a quick alphabetical list of simple ASCII-ish labels, but it is a trap for numbers.

Step through the default-sort surprise. Then switch to a numeric comparator and to one verified localeCompare example. Locale-aware sorting gets its own lesson next module, Comparing & sorting text; here we only preview the API so you know default sort is not the right tool for human language.

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

Predict the printed order, then step through the real run. Change the comparator and predict again.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const sorted = numbers.sort();console.log(sorted.join(", "));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Switch the one line that chooses the ordering rule.

Changing the rule starts a fresh replay. The locale example is a preview of the Comparing & sorting text lesson 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.
Real-life analogyA comparator is a referee

A sorting algorithm does not know what “best” means for your data. It asks your comparator to judge two items at a time. The comparator does not move items itself; it only answers who should come first.

In real life: The referee compares two players
In JavaScript: The comparator receives two items, a and b
In real life: Negative means player A goes first
In JavaScript: Return a negative number to put a before b
In real life: Positive means player B goes first
In JavaScript: Return a positive number to put b before a
In real life: A tie keeps the queue calm
In JavaScript: Return 0 when their order should be unchanged

Where the analogy stops: The engine may ask the referee about pairs in any order, and more than once. Do not write comparators with side effects that depend on the exact call sequence.

For numbers, the common ascending rule is (a, b) => a - b. If a is smaller, the subtraction is negative, so a goes first. For descending order, flip it to (a, b) => b - a. For objects, compare the property you care about, and use || for tie-breakers.

Comparator playground: sort people on a copy
Visible comparatorPop 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
Comparator calls in this browser (order can differ between engines)

Comparator-call log will appear after this runs in your browser.

Try it yourself
Sort by:Direction:

The playground uses [...people].sort(...), so the original data is copied first. Comparator-call details appear after the browser mounts.

A comparator is called many times. The engine chooses the algorithm; your job is to return a negative, positive, or zero number consistently.
Comparator bug: booleans are not enough

You may see (a, b) => a > b. It returns true or false, which become 1 and 0, but it never returns a negative number. It may appear to work in one small test and fail in another order. Return a number: negative, positive, or zero.

reverse and mutation

STEP THROUGH

reverse() is simpler than sort(): it flips the current order. But it shares the important danger: it mutates the original array and returns the same array object. That is fine for a local scratch array. It is dangerous for shared data, props, state, caches, or anything another reference might read later.

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

Step through the mutation demo. The important question is not only what prints, but whether a new array was created.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
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);
CallStoreChangeResultRun = next line. 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.

The shared-reference bug looks like this: one variable is named visibleProducts, another is named allProducts, but both point at the same array. Sorting the visible list in place silently rearranges the master list too. You learned in Objects & references that mutation is visible through every reference. Arrays follow the same rule because arrays are objects.

Copy before old mutating methodsPop 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]

Spread and slice() make shallow copies. Then sort() or reverse() can mutate the copy without touching the original array order. The objects inside are still shared; only the list container is copied.

toSorted, toReversed, toSpliced & with()

INTERACTIVE

Modern JavaScript gives copying versions of the mutation-heavy array operations. Think of sort() as rearranging the original bookshelf. toSorted() photographs the shelf and rearranges the photo. The shelf itself is unchanged.

Mutating method and copying partner
Change you wantMutates originalReturns a changed copy
Sort itemssort(compare)toSorted(compare)
Reverse itemsreverse()toReversed()
Remove or insertsplice(start, deleteCount, ...items)toSpliced(start, deleteCount, ...items)
Replace one indexarray[index] = valuewith(index, value)
Does this browser support 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 this browser's Array.prototype support…

Try it yourself
Checking browser support

Feature detection runs after the browser mounts, avoiding server/browser mismatches.

Feature detection checks the reader's real browser; the lesson tests verify the same behavior in Node.

with(index, value) deserves special attention because it replaces the common mutation arr[i] = value. It accepts negative indexes like at(): arr.with(-1, "done") replaces the last item. If the index is outside the array, it throws a RangeError instead of silently creating a sparse array.

Mutates 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

Sort each array operation by whether it changes the original array or returns a changed copy.

Choose a category for every card. You can change an answer at any time; Reset clears them all.
Browser support note

These copying methods are ES2023 features and are broadly supported in modern browsers. If you support older browsers, use feature detection, transpilation, or the spread-and-sort pattern until your baseline includes them.

Stable sorting

GUARANTEED

A stable sort keeps tied items in the order they arrived. Picture a queue: if Mia and Ava both have age 30, a stable age sort does not randomly swap them. JavaScript array sorting is stable in modern JavaScript (the guarantee has been part of the language since ES2019).

Stable age sortPop 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(", "));

The output is Noah, Leo, Mia, Ava. Noah and Leo both have age 20, and Noah arrived first, so he stays before Leo. Mia and Ava both have age 30, and Mia stays before Ava. Stability lets you sort by a secondary key first, then by a primary key, though a single comparator with an explicit tie-breaker is often clearer.

Real-life analogyStable sorting is a fair queue

At a help desk, people with the same priority keep the order they arrived in. Stability gives your equal items that same fairness.

In real life: People with the same ticket priority keep arrival order
In JavaScript: Items where comparator returns 0 keep relative order
In real life: Changing priority can move someone
In JavaScript: A nonzero comparator result can reorder items
In real life: The queue rule is predictable
In JavaScript: Stable sort makes tie behavior safe to rely on

Where the analogy stops: A queue has one obvious next person. A sorting algorithm may compare many pairs internally, so stability only describes the final order of ties, not the internal steps.

Where you'll use this

Sorting and safe copying show up anywhere UI data is displayed differently from how it is stored. A product page may keep server results untouched, then show price low-to-high. A table may let users click Name, Age, or Joined date without mutating the fetched data. A reducer may replace one cart item without changing the previous state object.

  • Sortable tables
  • Leaderboards
  • Search results
  • Shopping carts
  • Undo/redo state
  • Grouped reports
Safe table sortingPop 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;  });}

Notice the function returns a new array every time. That makes it friendly for UI state, testing, and undo history.

Common misconceptions

“sort knows numbers are numbers.”

Not by default. Without a comparator, defined values are sorted by string form, so 100 comes before 9.

“Returning true or false is a comparator.”

A comparator must return a number with negative, positive, and zero meanings. A boolean loses the negative case and is unreliable.

“sort and reverse give me a new array.”

They return the same array they mutate. Use toSorted, toReversed, or copy first.

“Stable means non-mutating.”

Stable only means tied items keep their relative order. sort is stable and mutating; toSorted is stable and copying.

“undefined sorts like the word undefined.”

Array sort places undefined elements after defined elements. If you log with join, they appear as empty fields.

Practice: sorting safely

5 EXERCISES
Exercise 1 · Warm-upSort numbers ascending

Run the code and check the printed result. Then try replacing toSorted with sort and explain what changes.

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

    Use a tie-breaker comparator to sort full names by last name, then first name.

    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 · PracticeFix an in-place sort bug

      A shared list must stay unchanged. Use a copying method so the first log 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 immutably

        Replace sugar with honey without mutating the original cart.

        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 default sort

          Before running it, predict exactly what the default sort prints.

          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, especially when your first guess is wrong. Sorting mistakes often look correct on tiny samples.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What does default sorting numbers 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 ascending?

              Choose an answer to see the explanation.

            3. Question 3 of 7What does identity after sort print?

              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 changed copy with one item replaced?

              Choose an answer to see the explanation.

            5. Question 5 of 7What do toReversed logs 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 guarantee?

              Choose an answer to see the explanation.

            7. Question 7 of 7What happens here?

              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 string forms, so numeric arrays need comparators.
            • A comparator returns negative for a first, positive for b first, and zero for a tie.
            • sort, reverse, splice, and index assignment mutate the original array.
            • toSorted, toReversed, toSpliced, and with return changed copies.
            • JavaScript sorting is stable, so tied items keep their relative order.

            Remember the one-liner.
            Sort with an explicit rule, and copy before you rearrange shared data.

            Up next: Iterating arrays & array-likes.

            CompleteFrontend Clear concepts. Working examples.