The New Immutable Array Methods
ES2023 introduced immutable versions of array methods that return new arrays instead of modifying the original. This eliminates an entire class of bugs:
const original = [3, 1, 4, 1, 5];
// Old (mutates original — dangerous!)
original.sort(); // original is now [1, 1, 3, 4, 5]
// New (returns a new sorted array)
const sorted = original.toSorted();
// original: [3, 1, 4, 1, 5] (unchanged)
// sorted: [1, 1, 3, 4, 5]
// Similarly:
const reversed = original.toReversed();
const spliced = original.toSpliced(1, 1); // Remove index 1
const changed = original.with(0, 99); // Replace index 0
Always prefer these over their mutating counterparts. They prevent bugs in React state updates, shared data structures, and functional pipelines.Object.groupBy — Finally Native Grouping
Previously you needed lodash's groupBy or wrote it yourself. Now it's built-in:
const users = [
{ name: 'Alice', role: 'admin' },
{ name: 'Bob', role: 'user' },
{ name: 'Charlie', role: 'admin' },
{ name: 'Diana', role: 'user' },
];
const grouped = Object.groupBy(users, user => user.role);
// {
// admin: [{ name: 'Alice', role: 'admin' }, { name: 'Charlie', role: 'admin' }],
// user: [{ name: 'Bob', role: 'user' }, { name: 'Diana', role: 'user' }]
// }
Real-world use cases:
// Group orders by status
const byStatus = Object.groupBy(orders, o => o.status);
// Group files by extension
const byType = Object.groupBy(files, f => f.name.split('.').pop());
// Group events by date
const byDate = Object.groupBy(events, e => e.date.toDateString());
Supported in all modern browsers and Node.js 21+.
findLast and findLastIndex
Search arrays from the end — useful when the last match is what you need:
const transactions = [
{ id: 1, type: 'credit', amount: 100 },
{ id: 2, type: 'debit', amount: 50 },
{ id: 3, type: 'credit', amount: 200 },
{ id: 4, type: 'debit', amount: 75 },
];
// Find the most recent credit
const lastCredit = transactions.findLast(t => t.type === 'credit');
// { id: 3, type: 'credit', amount: 200 }
// Find its index
const lastCreditIndex = transactions.findLastIndex(t => t.type === 'credit');
// 2
Before findLast, you had to reverse the array first or loop manually. This is cleaner and doesn't create a copy of the array.
Practical Patterns: Chaining Methods
The real power of array methods is chaining them for complex data transformations:
// Transform API response into UI-ready data
const tableData = rawUsers
.filter(user => user.status === 'active')
.toSorted((a, b) => b.lastLogin - a.lastLogin)
.slice(0, 10)
.map(user => ({
name: user.fullName,
email: user.email,
lastSeen: formatDate(user.lastLogin),
plan: user.plan.toUpperCase(),
}));
// Calculate statistics
const stats = orders
.filter(o => o.date >= startOfMonth)
.reduce((acc, order) => {
acc.total += order.amount;
acc.count += 1;
acc.avg = acc.total / acc.count;
return acc;
}, { total: 0, count: 0, avg: 0 });
// Flatten nested arrays
const allTags = posts
.flatMap(post => post.tags)
.filter((tag, i, arr) => arr.indexOf(tag) === i); // unique
Tip: For uniqueness, use [...new Set(array)] instead of the filter trick — it's faster and cleaner.Performance: When NOT to Chain
Method chaining creates intermediate arrays. For small arrays (< 10,000 items) this doesn't matter. For large arrays, consider:
// Bad: creates 3 intermediate arrays
const result = hugeArray
.filter(x => x.active)
.map(x => x.value)
.reduce((sum, v) => sum + v, 0);
// Better: single pass
let sum = 0;
for (const x of hugeArray) {
if (x.active) sum += x.value;
}
Rules of thumb:
- Under 10K items: chain freely, readability > performance
- Over 10K items: consider a single
reduceorfor...ofloop - Over 100K items: definitely use a single pass
- If you're calling this in a hot loop (60fps animation): always single pass