JavaScript Array sort, reverse, flat and flatMap Explained
Learn to sort arrays correctly in JavaScript with custom compare functions, reverse them, and flatten nested arrays with flat and flatMap, with real examples.
What you will learn
Lists need order. A shop shows the cheapest product first, a leaderboard shows the highest score first, a contact list is alphabetical. Sometimes your data is also a list of lists that you want to turn into a single list. In this lesson you will learn how sort really works (and why it breaks numbers by default), how to write custom compare functions for numbers, text, objects and multiple keys, how reverse works, and how flat and flatMap flatten nested arrays.
sort: the basics
const fruits = ["mango", "apple", "banana"];
fruits.sort();
console.log(fruits); // ["apple", "banana", "mango"]
Two very important facts:
sortchanges the original array (it is a mutating method). It also returns the same array.- By default, it sorts everything as text, in dictionary order of the character codes.
The number trap
Because the default is text order, numbers behave strangely:
console.log([10, 9, 1, 100, 25].sort());
// [1, 10, 100, 25, 9]
Why? It compares the numbers as text: "1" comes before "10", which comes before "100", then "25", and finally "9". Letters are compared one at a time, so "9" is “bigger” than "25" because "9" is bigger than "2".
Capital letters come first
console.log(["banana", "Apple", "cherry"].sort());
// ["Apple", "banana", "cherry"]
console.log(["b", "a", "C"].sort());
// ["C", "a", "b"] (uppercase letters sort before lowercase ones)
Both traps have the same cure: a compare function.
The compare function
sort accepts a callback with two parameters, a and b, which are two items being compared. Your function must return a number:
| Return value | Meaning |
|---|---|
| Negative (less than 0) | a should come before b |
| Positive (greater than 0) | b should come before a |
| Zero | Keep their order as it is |
Numbers: ascending and descending
const numbers = [10, 9, 1, 100, 25];
// Smallest to largest
console.log([...numbers].sort((a, b) => a - b)); // [1, 9, 10, 25, 100]
// Largest to smallest
console.log([...numbers].sort((a, b) => b - a)); // [100, 25, 10, 9, 1]
Why does a - b work? If a is smaller than b, then a - b is negative, so a goes first. If a is bigger, the result is positive, so b goes first. For descending order, just swap them: b - a.
Memory trick:
a - bgives ascending order (small to large). Swap the letters,b - a, for descending order.
Copy first if you need the original
Since sort changes the array, copy it before sorting when you still need the old order:
const scores = [72, 95, 60];
const sorted = [...scores].sort((a, b) => b - a);
console.log(sorted); // [95, 72, 60]
console.log(scores); // [72, 95, 60] (still in the original order)
Newer JavaScript has toSorted(), which does the copy for you:
const scores = [72, 95, 60];
const sorted2 = scores.toSorted((a, b) => b - a);
console.log(sorted2); // [95, 72, 60]
console.log(scores); // [72, 95, 60]
Text: use localeCompare
For alphabetical sorting that respects language rules, use localeCompare. It returns a negative, zero or positive number, exactly what sort wants:
const names = ["Zara", "amit", "Bina", "Éric", "chirag"];
console.log([...names].sort());
// ["Bina", "Zara", "amit", "chirag", "Éric"] (capitals first, accents last)
console.log([...names].sort((a, b) => a.localeCompare(b)));
// ["amit", "Bina", "chirag", "Éric", "Zara"] (a proper dictionary order)
Sorting text that contains numbers (natural sort)
const files = ["file10.txt", "file2.txt", "file1.txt"];
console.log([...files].sort());
// ["file1.txt", "file10.txt", "file2.txt"] (10 before 2: wrong for humans)
console.log(
[...files].sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
);
// ["file1.txt", "file2.txt", "file10.txt"]
The option { numeric: true } tells localeCompare to read digits as numbers.
Sorting objects by a property
const products = [
{ name: "Headphones", price: 1500 },
{ name: "Pen", price: 20 },
{ name: "Backpack", price: 900 }
];
// By price, cheapest first
const byPrice = [...products].sort((a, b) => a.price - b.price);
console.log(byPrice.map((p) => p.name)); // ["Pen", "Backpack", "Headphones"]
// By name, A to Z
const byName = [...products].sort((a, b) => a.name.localeCompare(b.name));
console.log(byName.map((p) => p.name)); // ["Backpack", "Headphones", "Pen"]
Sorting by more than one key
Sort by category first, and use the price when the categories are the same. The trick is that || moves to the next rule when the first result is 0:
const items = [
{ name: "Mouse", category: "electronics", price: 600 },
{ name: "Pen", category: "stationery", price: 20 },
{ name: "Headphones", category: "electronics", price: 1500 },
{ name: "Notebook", category: "stationery", price: 80 }
];
const sorted = [...items].sort(
(a, b) => a.category.localeCompare(b.category) || a.price - b.price
);
console.log(sorted.map((i) => i.name));
// ["Mouse", "Headphones", "Pen", "Notebook"]
A custom order
Sometimes the order is not alphabetical or numeric. Define it with an array and compare positions:
const tasks = [
{ title: "Fix bug", priority: "medium" },
{ title: "Write test", priority: "low" },
{ title: "Deploy", priority: "high" }
];
const order = ["high", "medium", "low"];
tasks.sort((a, b) => order.indexOf(a.priority) - order.indexOf(b.priority));
console.log(tasks.map((t) => t.title)); // ["Deploy", "Fix bug", "Write test"]
Sorting dates
Dates written in the ISO format (2026-10-01) sort correctly as text. For real Date objects, subtract them:
const posts = [
{ title: "A", date: "2026-09-25" },
{ title: "B", date: "2026-10-01" },
{ title: "C", date: "2026-08-10" }
];
// Newest first
const newest = [...posts].sort((a, b) => new Date(b.date) - new Date(a.date));
console.log(newest.map((p) => p.title)); // ["B", "A", "C"]
(Dates have their own lesson later.)
Other things to know
- Stable sort: items that compare as equal keep their original order. Modern JavaScript guarantees this.
undefinedgoes last:[3, undefined, 1].sort()gives[1, 3, undefined], and your compare function is not called for it.- A wrong compare function (for example one that returns only
trueorfalse) gives unpredictable results. Always return a number.
reverse: flip the order
reverse() turns the array around. Like sort, it changes the original array.
const letters = ["a", "b", "c", "d"];
letters.reverse();
console.log(letters); // ["d", "c", "b", "a"]
To keep the original, copy first (or use the newer toReversed()):
const original = [1, 2, 3];
const flipped = [...original].reverse();
console.log(flipped); // [3, 2, 1]
console.log(original); // [1, 2, 3]
reverse is not the same as sorting descending
reverse just flips whatever order exists. It does not sort:
console.log([3, 1, 2].reverse()); // [2, 1, 3] (flipped, not sorted)
console.log([3, 1, 2].sort((a, b) => b - a)); // [3, 2, 1] (sorted, largest first)
A classic use is reversing text:
console.log("hello".split("").reverse().join("")); // "olleh"
flat: flatten nested arrays
Sometimes you have arrays inside arrays. flat pulls the inner items out into one array. It returns a new array.
const nested = [1, [2, 3], [4, 5]];
console.log(nested.flat()); // [1, 2, 3, 4, 5]
Depth
By default, flat goes only one level deep. Give it a depth to go deeper:
const deep = [1, [2, [3, [4]]]];
console.log(deep.flat()); // [1, 2, [3, [4]]] (one level)
console.log(deep.flat(2)); // [1, 2, 3, [4]] (two levels)
console.log(deep.flat(Infinity)); // [1, 2, 3, 4] (all levels)
flat also removes empty holes
console.log([1, , 3].flat()); // [1, 3]
flatMap: map, then flatten
flatMap runs a map and then flattens the result by one level. It is useful when each item turns into several items (or none).
Compare:
const sentences = ["Hello world", "I love JS"];
// map gives an array of arrays
console.log(sentences.map((s) => s.split(" ")));
// [["Hello", "world"], ["I", "love", "JS"]]
// flatMap gives one flat array
console.log(sentences.flatMap((s) => s.split(" ")));
// ["Hello", "world", "I", "love", "JS"]
Filter and transform in one step
If your callback returns an empty array [] for an item, that item disappears. So flatMap can filter and map at the same time:
const inputs = ["10", "abc", "20", "x"];
const numbers = inputs.flatMap((text) => {
const n = Number(text);
return Number.isNaN(n) ? [] : [n]; // skip bad values, wrap good ones
});
console.log(numbers); // [10, 20]
Make several items from one
const orders = [
{ item: "Pen", qty: 2 },
{ item: "Book", qty: 1 }
];
const labels = orders.flatMap((o) => Array(o.qty).fill(o.item));
console.log(labels); // ["Pen", "Pen", "Book"]
(Array(2).fill("Pen") makes ["Pen", "Pen"].)
Real-life use cases
1. Leaderboard: top 3 players
const players = [
{ name: "Riya", score: 820 },
{ name: "Karan", score: 950 },
{ name: "Neha", score: 760 },
{ name: "Aman", score: 890 }
];
const top3 = [...players]
.sort((a, b) => b.score - a.score)
.slice(0, 3)
.map((p, i) => `${i + 1}. ${p.name} (${p.score})`);
console.log(top3);
// ["1. Karan (950)", "2. Aman (890)", "3. Riya (820)"]
2. A table that sorts by any column
function sortBy(list, key, direction = "asc") {
const sorted = [...list].sort((a, b) => {
if (typeof a[key] === "string") {
return a[key].localeCompare(b[key]);
}
return a[key] - b[key];
});
return direction === "desc" ? sorted.reverse() : sorted;
}
const students = [
{ name: "Riya", marks: 82 },
{ name: "Karan", marks: 67 },
{ name: "Neha", marks: 91 }
];
console.log(sortBy(students, "marks", "desc").map((s) => s.name)); // ["Neha", "Riya", "Karan"]
console.log(sortBy(students, "name").map((s) => s.name)); // ["Karan", "Neha", "Riya"]
One function powers a “click a column header to sort” feature.
3. Newest posts first
const posts = [
{ title: "Closures", date: "2026-09-25" },
{ title: "Arrays", date: "2026-10-01" },
{ title: "Loops", date: "2026-08-10" }
];
const latest = [...posts].sort((a, b) => b.date.localeCompare(a.date));
console.log(latest.map((p) => p.title)); // ["Arrays", "Closures", "Loops"]
4. Collect all tags from a list of posts
const posts = [
{ title: "Arrays", tags: ["js", "arrays"] },
{ title: "Closures", tags: ["js", "functions"] },
{ title: "Loops", tags: ["js", "basics"] }
];
const allTags = posts.flatMap((post) => post.tags);
console.log(allTags);
// ["js", "arrays", "js", "functions", "js", "basics"]
const uniqueTags = [...new Set(allTags)].sort();
console.log(uniqueTags); // ["arrays", "basics", "functions", "js"]
5. Flatten a menu with sub-menus
const menu = [
["Home"],
["Courses", ["HTML", "CSS", "JavaScript"]],
["Contact"]
];
console.log(menu.flat(Infinity));
// ["Home", "Courses", "HTML", "CSS", "JavaScript", "Contact"]
6. Words in a text, sorted and without duplicates
const text = "the cat and the hat and the bat";
const words = [...new Set(text.split(" "))].sort();
console.log(words); // ["and", "bat", "cat", "hat", "the"]
7. Median of a list
const values = [7, 1, 9, 3, 5];
const sorted = [...values].sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
const median =
sorted.length % 2 === 0
? (sorted[middle - 1] + sorted[middle]) / 2
: sorted[middle];
console.log(median); // 5
8. Shuffle a list (the right way)
You may see array.sort(() => Math.random() - 0.5) online. It looks clever, but it gives biased results, so some orders appear more often than others. Use the Fisher-Yates method instead:
function shuffle(array) {
const copy = [...array];
for (let i = copy.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[copy[i], copy[j]] = [copy[j], copy[i]]; // swap two items
}
return copy;
}
console.log(shuffle([1, 2, 3, 4, 5])); // for example [3, 5, 1, 4, 2]
The line with the square brackets swaps two items using destructuring, which you will learn soon.
9. Sort by the length of words
const words = ["banana", "kiwi", "apple", "fig"];
const byLength = [...words].sort((a, b) => a.length - b.length);
console.log(byLength); // ["fig", "kiwi", "apple", "banana"]
Common mistakes
- Sorting numbers without a compare function.
[10, 9, 1].sort()gives[1, 10, 9]. Use(a, b) => a - b. - Forgetting that
sortandreversechange the original. Copy first with[...array], or usetoSorted()/toReversed(). - Writing a compare function that returns
trueorfalse. It must return a number (negative, zero or positive). - Using
a > binstead ofa - bas the compare function. It does not work reliably. - Sorting text with capital letters using the default. Use
localeCompare. - Using
reverseand expecting it to sort. It only flips the current order. - Using
a.price - b.pricewhen the values are text. Convert them withNumber()first, or the result may beNaN. - Using
sort(() => Math.random() - 0.5)to shuffle. It is biased. Use Fisher-Yates. - Forgetting that
flat()goes one level deep. Give a depth, orInfinity. - Using
flatMapfor deeper flattening. It flattens only one level. - Sorting an array of objects by a property that is missing on some items. The result can be
NaN, and the order becomes unpredictable. Give the items a default value first.
Practice
- Sort
[40, 5, 100, 25, 1]in ascending and in descending order, without changing the original array. - Sort
["pear", "Apple", "banana", "cherry"]alphabetically, ignoring the difference between capital and small letters. - Sort a list of 4 books (
title,pages) by number of pages, from the thickest to the thinnest. - Sort the same books by title A to Z.
- Given a list of students with
grade(“A”, “B”, “C”) andname, sort them by grade and then by name. - Reverse the array
[1, 2, 3, 4, 5], first changing the original, and then keeping the original untouched. - Flatten
[1, [2, [3, [4, [5]]]]]to[1, 2, 3, 4, 5]. - Use
flatMapto turn["a b", "c d e"]into["a", "b", "c", "d", "e"]. - Use
flatMapto double only the even numbers of[1, 2, 3, 4], so the result is[4, 8]. - Challenge: write
sortByMany(list, rules)that sorts by several keys, for example[["category", "asc"], ["price", "desc"]].
Recap
sort()sorts in place and, by default, as text. Always pass a compare function for numbers:(a, b) => a - bfor ascending,(a, b) => b - afor descending.- The compare function returns a negative number (
afirst), positive (bfirst) or zero. - Use
localeComparefor text (with{ numeric: true }for names likefile10),a.price - b.pricefor objects, and||to sort by several keys. sortandreversechange the original array. Copy first with[...array], or usetoSorted()andtoReversed().reverse()only flips the order. It does not sort.flat(depth)flattens nested arrays (one level by default,Infinityfor all levels) and returns a new array.flatMap(fn)ismapfollowed by a one-levelflat. Return[]to drop an item, or an array to create several.- Do not shuffle with
sort(() => Math.random() - 0.5). Use Fisher-Yates. - Next you will learn objects: properties, methods and the way real data is organised.