Sorting, Searching & Modern Additions

Intermediate · 18 min read · ▶ live playground · ✦ checkpoint

Sorting is where arrays show one of JavaScript's classic beginner traps: sort() does not sort numbers numerically unless you tell it how. This lesson closes Section 6 with the sorting rule, modern non-mutating alternatives, the last few search helpers, and a small preview of grouping values into buckets.

sort() uses text order by default

sort() mutates the array and, by default, compares values as strings. That is fine for simple words, but surprising for numbers.

const scores = [10, 2, 30];
 
scores.sort();
 
console.log(scores[0]);
console.log(scores[1]);
console.log(scores[2]);

10, 2, 30 — the values were compared like text, not like numbers.

A comparator is a callback that tells JavaScript how two items should be ordered. For numbers, the standard ascending comparator is a - b.

const scores = [10, 2, 30];
 
scores.sort((a, b) => a - b);
 
console.log(scores[0]);
console.log(scores[1]);
console.log(scores[2]);

2, 10, 30.

You do not need the full sorting algorithm in your head. The practical rule is enough:

  • Return a negative number when a should come before b.
  • Return a positive number when a should come after b.
  • Return 0 when their order is tied.

That is why a - b sorts numbers ascending, and b - a sorts numbers descending.

Mutation vs modern copies

sort() changes the original array. That is sometimes what you want, but it is dangerous when another part of your program still expects the old order.

const original = [10, 2, 30];
const alias = original;
 
original.sort((a, b) => a - b);
 
console.log(original[0]);
console.log(alias[0]);

2, then 2 — both names point at the same mutated array.

Modern JavaScript gives you copy-first methods for common "same array, changed shape" tasks:

const scores = [10, 2, 30];
 
const sorted = scores.toSorted((a, b) => a - b);
const reversed = scores.toReversed();
const corrected = scores.with(1, 20);
 
console.log(scores[0]);
console.log(scores[1]);
console.log(scores[2]);
console.log(sorted[0]);
console.log(reversed[0]);
console.log(corrected[1]);

→ the original still prints 10, 2, 30; the copies start with 2, 30, and replacement index 1 is 20.

Use toSorted when you want a sorted copy, toReversed when you want a reversed copy, and with(index, value) when you want a copy with one slot replaced. They fit the copy-vs-alias habit from 6.1: keep the original stable unless mutation is truly the point.

Search answers: any, all, last, and from the end

You already know includes, find, and findIndex. These helpers cover the common questions those do not quite express.

const scores = [6, 12, 18, 9];
 
console.log(scores.some(score => score >= 18));
console.log(scores.every(score => score >= 10));
console.log(scores.at(0));
console.log(scores.at(-1));
console.log(scores.findLast(score => score < 10));

true, false, 6, 9, 9.

Read them in plain English:

  • some(callback) asks, "Does at least one item pass?"
  • every(callback) asks, "Do all items pass?"
  • at(index) reads an index, and negative indexes count from the end.
  • findLast(callback) searches from the end and returns the last matching value.

some and every return booleans. findLast returns a value or undefined, just like find.

Try it — choose mutating or copy-first

Run this, then change the threshold and comparator direction. At the prompt, inspect scores, ranked, and fixed.

javascript — live consolereal engine
⌘/Ctrl + Enter to run

Notice the pattern: the sorted and fixed arrays are new arrays, while scores remains in its original order. That makes experiments safer because you can compare "before" and "after" values in the same run.

Group values into buckets

Grouping means taking one flat array and making named buckets from it. Full object mechanics arrive in Section 7, but you can read this first example as: Object.groupBy returns a named-bucket value, and groups.passing reads the bucket named passing.

const scores = [6, 12, 18, 9];
const groups = Object.groupBy(scores, score => {
  return score >= 10 ? "passing" : "needsPractice";
});
 
console.log(groups.passing.length);
console.log(groups.passing[0]);
console.log(groups.needsPractice.length);
console.log(groups.needsPractice[0]);
 
const names = ["Mina", "Sol", "Rae"];
const byLength = Map.groupBy(names, name => name.length);
 
console.log(byLength.get(3).length);
console.log(byLength.get(3)[0]);
console.log(byLength.get(4)[0]);

→ the passing bucket has 2 items starting with 12; the needsPractice bucket has 2 items starting with 6. Then length 3 has 2 names starting with Sol; length 4 starts with Mina.

Map.groupBy makes buckets in a Map, a key-value collection you will study in Section 8. The useful preview is that a Map can use keys that are not just property names, and .get(key) reads a bucket.

Do not rush to group everything. Reach for grouping when the buckets are the point: passing vs needs practice, short vs long names, morning vs evening shifts. If you only need one transformed list, map or filter is still simpler.

Checkpoint

Answer all three to mark this lesson complete

Section 6 gave you arrays as ordered data plus the transformation tools professionals use daily. Next, Section 7 turns to objects, so you can model values with named properties instead of only positions.

+50 XP on completion