Skip to main content

How to Use Array Methods in JavaScript

Arrays in JavaScript come with a powerful set of built-in methods that handle virtually every data manipulation task you will encounter. From adding and removing elements to searching, transforming, filtering, sorting, and reducing data, these methods form the essential toolkit for working with collections.

Mastering array methods is one of the most impactful skills a JavaScript developer can build. They replace complex manual loops with expressive, readable, and often more efficient code. Instead of writing ten lines with a for loop, index management, and temporary variables, you can express the same logic in a single method call that clearly communicates your intent.

This guide covers every major array method in JavaScript, organized by purpose. Each method includes syntax, behavior, practical examples, and the critical distinction between methods that mutate the original array and those that return a new one.

Adding/Removing: splice, slice, concat

These three methods handle the core operations of adding, removing, and extracting elements from arrays. Despite their similar names, splice and slice behave very differently.

splice(start, deleteCount, ...items): The Swiss Army Knife

splice is the most versatile array modification method. It can remove elements, insert elements, or replace elements, all in a single call. It mutates the original array and returns an array of the removed elements.

let arr = ["a", "b", "c", "d", "e"];

// Remove 2 elements starting at index 1
let removed = arr.splice(1, 2);
console.log(arr); // ["a", "d", "e"]
console.log(removed); // ["b", "c"]

Insert without removing (set deleteCount to 0):

let arr = ["a", "d", "e"];

// Insert "b" and "c" at index 1, delete 0 elements
arr.splice(1, 0, "b", "c");
console.log(arr); // ["a", "b", "c", "d", "e"]

Replace elements:

let arr = ["a", "b", "c", "d", "e"];

// At index 1, remove 2 elements and insert "X", "Y", "Z"
arr.splice(1, 2, "X", "Y", "Z");
console.log(arr); // ["a", "X", "Y", "Z", "d", "e"]

Negative indices count from the end:

let arr = [1, 2, 3, 4, 5];

arr.splice(-2, 1); // Remove 1 element, 2 from the end
console.log(arr); // [1, 2, 3, 5]

Delete all from a position:

let arr = [1, 2, 3, 4, 5];

arr.splice(2); // Remove everything from index 2 onward
console.log(arr); // [1, 2]

slice(start, end): Extract Without Mutating

slice returns a new array containing elements from start up to (but not including) end. The original array is not modified.

let arr = ["a", "b", "c", "d", "e"];

console.log(arr.slice(1, 3)); // ["b", "c"]
console.log(arr.slice(2)); // ["c", "d", "e"] (from index 2 to end)
console.log(arr.slice(-2)); // ["d", "e"] (last 2 elements)
console.log(arr.slice(-3, -1)); // ["c", "d"]
console.log(arr.slice()); // ["a", "b", "c", "d", "e"] (shallow copy)
console.log(arr); // ["a", "b", "c", "d", "e"] (unchanged!)

slice() with no arguments creates a shallow copy of the entire array, which is a common pattern:

let original = [1, 2, 3];
let copy = original.slice();

copy.push(4);
console.log(original); // [1, 2, 3] (unaffected)
console.log(copy); // [1, 2, 3, 4]

concat(...items): Merge Arrays

concat creates a new array by merging the original with other arrays or values. It does not mutate the original.

let arr1 = [1, 2];
let arr2 = [3, 4];
let arr3 = [5, 6];

let merged = arr1.concat(arr2, arr3);
console.log(merged); // [1, 2, 3, 4, 5, 6]
console.log(arr1); // [1, 2] (unchanged)

// Can also add individual values
let extended = arr1.concat(3, 4, [5, 6]);
console.log(extended); // [1, 2, 3, 4, 5, 6]

// Only flattens one level
let nested = arr1.concat([3, [4, 5]]);
console.log(nested); // [1, 2, 3, [4, 5]]

The spread operator provides a modern alternative:

let merged = [...arr1, ...arr2, ...arr3];
console.log(merged); // [1, 2, 3, 4, 5, 6]

splice vs. slice Quick Reference

Featurespliceslice
Mutates originalYesNo
ReturnsRemoved elementsNew array (extracted portion)
Can insert/replaceYesNo
Common useModify array in placeExtract sub-array or copy

Searching: indexOf, lastIndexOf, includes

These methods search for a specific value in an array using strict equality (===).

indexOf(item, fromIndex)

Returns the first index where the item is found, or -1 if not found:

let arr = [1, 2, 3, 2, 1];

console.log(arr.indexOf(2)); // 1 (first occurrence)
console.log(arr.indexOf(2, 2)); // 3 (search starting from index 2)
console.log(arr.indexOf(99)); // -1 (not found)

lastIndexOf(item, fromIndex)

Returns the last index where the item is found:

let arr = [1, 2, 3, 2, 1];

console.log(arr.lastIndexOf(2)); // 3 (last occurrence)
console.log(arr.lastIndexOf(2, 2)); // 1 (searching backward from index 2)

includes(item, fromIndex)

Returns true or false. Simpler than checking indexOf !== -1:

let arr = [1, 2, 3, NaN];

console.log(arr.includes(2)); // true
console.log(arr.includes(99)); // false

// includes correctly finds NaN (indexOf cannot!)
console.log(arr.includes(NaN)); // true
console.log(arr.indexOf(NaN)); // -1 (indexOf FAILS with NaN!)
tip

Use includes when you just need to know if a value exists. Use indexOf when you need the position. Note that includes correctly handles NaN, while indexOf does not.

Finding: find, findIndex, findLast, findLastIndex

These methods search using a callback function, making them far more flexible than indexOf/includes. They are essential for finding objects in arrays.

find(callback)

Returns the first element that satisfies the condition, or undefined if none match:

let users = [
{ id: 1, name: "Alice", age: 30 },
{ id: 2, name: "Bob", age: 25 },
{ id: 3, name: "Charlie", age: 35 }
];

let user = users.find(u => u.age > 28);
console.log(user); // { id: 1, name: "Alice", age: 30 }

let missing = users.find(u => u.age > 100);
console.log(missing); // undefined

findIndex(callback)

Returns the index of the first matching element, or -1:

let users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
];

let index = users.findIndex(u => u.name === "Bob");
console.log(index); // 1

let missing = users.findIndex(u => u.name === "Dave");
console.log(missing); // -1

findLast(callback) and findLastIndex(callback) (ES2023)

Search from the end of the array:

let numbers = [1, 5, 10, 15, 20, 25];

let lastUnder20 = numbers.findLast(n => n < 20);
console.log(lastUnder20); // 15

let lastUnder20Index = numbers.findLastIndex(n => n < 20);
console.log(lastUnder20Index); // 3

These are particularly useful when you know the element you want is closer to the end, or when you need the last match specifically.

find vs. filter

find returns the first match (a single element). filter returns all matches (an array). Choose based on whether you need one result or all results:

let numbers = [1, 5, 10, 15, 20, 25];

let firstOver10 = numbers.find(n => n > 10);
console.log(firstOver10); // 15 (single value)

let allOver10 = numbers.filter(n => n > 10);
console.log(allOver10); // [15, 20, 25] (array of all matches)

Transforming: map for creating new arrays

map creates a new array by applying a function to every element. It is one of the most frequently used array methods.

let numbers = [1, 2, 3, 4, 5];

let doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5] (unchanged)

Practical Examples

// Extract specific properties
let users = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
{ name: "Charlie", age: 35 }
];

let names = users.map(u => u.name);
console.log(names); // ["Alice", "Bob", "Charlie"]

// Transform data format
let prices = [10, 20, 30];
let formatted = prices.map(p => `$${p.toFixed(2)}`);
console.log(formatted); // ["$10.00", "$20.00", "$30.00"]

// Create objects from values
let ids = [1, 2, 3];
let objects = ids.map(id => ({ id, active: true }));
console.log(objects);
// [{ id: 1, active: true }, { id: 2, active: true }, { id: 3, active: true }]

// Using index (second callback argument)
let letters = ["a", "b", "c"];
let indexed = letters.map((letter, i) => `${i}: ${letter}`);
console.log(indexed); // ["0: a", "1: b", "2: c"]
map Should Return a Value

map is for transforming data. If you are using map without returning anything (just for side effects), use forEach instead. map without a return creates an array of undefined values.

// ❌ WRONG: Using map for side effects
users.map(u => console.log(u.name)); // Returns [undefined, undefined, undefined]

// ✅ CORRECT: Use forEach for side effects
users.forEach(u => console.log(u.name));

Filtering: filter for selecting elements

filter creates a new array containing only elements that pass a test function:

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

let evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]

let overFive = numbers.filter(n => n > 5);
console.log(overFive); // [6, 7, 8, 9, 10]

Filtering Objects

let products = [
{ name: "Laptop", price: 999, inStock: true },
{ name: "Phone", price: 699, inStock: false },
{ name: "Tablet", price: 499, inStock: true },
{ name: "Watch", price: 299, inStock: true }
];

let available = products.filter(p => p.inStock);
console.log(available.length); // 3

let affordable = products.filter(p => p.price < 500 && p.inStock);
console.log(affordable);
// [{ name: "Tablet", price: 499, inStock: true }, { name: "Watch", price: 299, inStock: true }]

Removing Falsy Values

let messy = [0, "hello", "", null, undefined, 42, false, "world", NaN];

let clean = messy.filter(Boolean);
console.log(clean); // ["hello", 42, "world"]

Boolean as a function returns true for truthy values and false for falsy ones, making it a concise filter predicate.

Removing Duplicates (Without Set)

let numbers = [1, 2, 3, 2, 1, 4, 5, 3];

let unique = numbers.filter((value, index, arr) => {
return arr.indexOf(value) === index;
});
console.log(unique); // [1, 2, 3, 4, 5]

Reducing: reduce, reduceRight, Accumulating Values

reduce is the most powerful array method. It processes each element and accumulates a single result, which can be any type: a number, string, object, or even another array.

Syntax

array.reduce((accumulator, currentValue, index, array) => {
// return updated accumulator
}, initialValue);

Basic Examples

// Sum all numbers
let numbers = [1, 2, 3, 4, 5];

let sum = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum); // 15

// How it works step by step:
// acc=0, num=1 → return 1
// acc=1, num=2 → return 3
// acc=3, num=3 → return 6
// acc=6, num=4 → return 10
// acc=10, num=5 → return 15

// Product
let product = numbers.reduce((acc, num) => acc * num, 1);
console.log(product); // 120

// Maximum value
let max = numbers.reduce((acc, num) => num > acc ? num : acc, -Infinity);
console.log(max); // 5

Reducing to Objects

// Count occurrences
let fruits = ["apple", "banana", "apple", "cherry", "banana", "apple"];

let counts = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
console.log(counts); // { apple: 3, banana: 2, cherry: 1 }

Reducing to Arrays (Alternative to filter + map)

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Get doubled values of even numbers (filter + map in one pass)
let result = numbers.reduce((acc, num) => {
if (num % 2 === 0) {
acc.push(num * 2);
}
return acc;
}, []);
console.log(result); // [4, 8, 12, 16, 20]

Grouping with reduce

let people = [
{ name: "Alice", department: "Engineering" },
{ name: "Bob", department: "Marketing" },
{ name: "Charlie", department: "Engineering" },
{ name: "Diana", department: "Marketing" },
{ name: "Eve", department: "Design" }
];

let byDepartment = people.reduce((groups, person) => {
let dept = person.department;
if (!groups[dept]) groups[dept] = [];
groups[dept].push(person);
return groups;
}, {});

console.log(byDepartment);
// {
// Engineering: [{ name: "Alice"... }, { name: "Charlie"... }],
// Marketing: [{ name: "Bob"... }, { name: "Diana"... }],
// Design: [{ name: "Eve"... }]
// }

reduceRight

Works the same as reduce but processes elements from right to left:

let arr = [[1, 2], [3, 4], [5, 6]];

let flattened = arr.reduceRight((acc, val) => acc.concat(val), []);
console.log(flattened); // [5, 6, 3, 4, 1, 2]

Without Initial Value

If no initial value is provided, the first element becomes the initial accumulator and iteration starts from the second element:

let numbers = [1, 2, 3, 4, 5];

let sum = numbers.reduce((acc, num) => acc + num);
console.log(sum); // 15 (works, but...)

// ❌ DANGER: Throws on empty arrays without initial value!
// [].reduce((acc, num) => acc + num);
// TypeError: Reduce of empty array with no initial value

// ✅ SAFE: Always provide an initial value
[].reduce((acc, num) => acc + num, 0); // 0
Always Provide an Initial Value

Calling reduce on an empty array without an initial value throws a TypeError. Always provide the second argument to reduce to handle edge cases safely.

Testing: every, some

These methods test whether array elements satisfy a condition, returning a boolean.

every(callback): Do ALL Elements Pass?

Returns true only if every element passes the test:

let numbers = [2, 4, 6, 8, 10];

console.log(numbers.every(n => n % 2 === 0)); // true (all are even)
console.log(numbers.every(n => n > 5)); // false (2 and 4 fail)

let ages = [18, 21, 25, 30];
console.log(ages.every(age => age >= 18)); // true (all adults)

// Empty arrays always return true (vacuous truth)
console.log([].every(n => n > 100)); // true

some(callback): Does ANY Element Pass?

Returns true if at least one element passes the test:

let numbers = [1, 3, 5, 7, 8];

console.log(numbers.some(n => n % 2 === 0)); // true (8 is even)
console.log(numbers.some(n => n > 100)); // false (none pass)

// Practical: check for errors
let responses = [200, 200, 404, 200];
let hasError = responses.some(code => code >= 400);
console.log(hasError); // true

// Empty arrays always return false
console.log([].some(n => n > 0)); // false

Short-Circuit Behavior

Both methods stop early once the result is determined:

let data = [1, 2, 3, 4, 5];

// every stops at the first false
data.every(n => {
console.log(`Checking ${n}`);
return n < 3;
});
// Checking 1
// Checking 2
// Checking 3 (stops here (returned false))

// some stops at the first true
data.some(n => {
console.log(`Checking ${n}`);
return n > 3;
});
// Checking 1
// Checking 2
// Checking 3
// Checking 4 (stops here (returned true))

Iterating: forEach

forEach executes a function for each element. It is purely for side effects and always returns undefined.

let fruits = ["apple", "banana", "cherry"];

fruits.forEach((fruit, index) => {
console.log(`${index}: ${fruit}`);
});
// 0: apple
// 1: banana
// 2: cherry

forEach vs. for...of

The key limitation of forEach is that you cannot use break, continue, or return to control the loop flow:

// ❌ Cannot break out of forEach
let numbers = [1, 2, 3, 4, 5];

numbers.forEach(n => {
if (n === 3) return; // This only skips the current callback, doesn't stop the loop!
console.log(n);
});
// 1, 2, 4, 5 ("return" acts like "continue", not "break")

// ✅ Use for...of when you need break
for (let n of numbers) {
if (n === 3) break;
console.log(n);
}
// 1, 2

Use forEach for simple iteration where you need to process every element. Use for...of when you might need to stop early.

Sorting: sort, Custom Comparators, Stability, and Gotchas

The sort method sorts the array in place (mutates) and returns the sorted array.

The Default Sorting Gotcha

Without a comparator function, sort converts elements to strings and sorts them lexicographically (by Unicode code point order):

let numbers = [10, 9, 80, 1, 21, 3];

numbers.sort();
console.log(numbers); // [1, 10, 21, 3, 80, 9] (WRONG for numeric sorting!)
// "10" < "21" < "3" < "80" < "9" in string comparison

This is one of the most common JavaScript gotchas. Always provide a comparator for numeric sorting.

Numeric Sorting

let numbers = [10, 9, 80, 1, 21, 3];

// Ascending
numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 3, 9, 10, 21, 80]

// Descending
numbers.sort((a, b) => b - a);
console.log(numbers); // [80, 21, 10, 9, 3, 1]

The comparator function should return:

  • A negative number if a should come before b
  • Zero if they are equal
  • A positive number if a should come after b

Sorting Strings

let words = ["banana", "Apple", "cherry", "date"];

// Default: uppercase letters come before lowercase (by code point)
words.sort();
console.log(words); // ["Apple", "banana", "cherry", "date"]

// Case-insensitive sorting
words.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
console.log(words); // ["Apple", "banana", "cherry", "date"]

// Locale-aware sorting
words.sort((a, b) => a.localeCompare(b));
console.log(words); // ["Apple", "banana", "cherry", "date"]

Sorting Objects

let users = [
{ name: "Charlie", age: 35 },
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 }
];

// Sort by age (ascending)
users.sort((a, b) => a.age - b.age);
console.log(users.map(u => u.name)); // ["Bob", "Alice", "Charlie"]

// Sort by name (alphabetical)
users.sort((a, b) => a.name.localeCompare(b.name));
console.log(users.map(u => u.name)); // ["Alice", "Bob", "Charlie"]

Stability

Modern JavaScript engines guarantee stable sorting: elements that are equal according to the comparator maintain their original relative order. This has been required since ES2019.

let items = [
{ name: "A", priority: 1 },
{ name: "B", priority: 2 },
{ name: "C", priority: 1 },
{ name: "D", priority: 2 }
];

items.sort((a, b) => a.priority - b.priority);
console.log(items.map(i => i.name)); // ["A", "C", "B", "D"]
// A and C (both priority 1) kept their original order
// B and D (both priority 2) kept their original order
sort() Mutates the Original Array

sort() modifies the array in place. If you need to keep the original unchanged, copy first: let sorted = [...arr].sort(...) or use toSorted() (ES2023).

Reversing: reverse, toReversed

reverse(): Mutating

Reverses the array in place:

let arr = [1, 2, 3, 4, 5];

arr.reverse();
console.log(arr); // [5, 4, 3, 2, 1] (mutated!)

toReversed(): Non-Mutating (ES2023)

Returns a new reversed array without modifying the original:

let arr = [1, 2, 3, 4, 5];

let reversed = arr.toReversed();
console.log(reversed); // [5, 4, 3, 2, 1]
console.log(arr); // [1, 2, 3, 4, 5] (unchanged!)

Flattening: flat, flatMap

flat(depth)

Creates a new array with nested arrays flattened to the specified depth:

let nested = [1, [2, 3], [4, [5, 6]]];

console.log(nested.flat()); // [1, 2, 3, 4, [5, 6]] (depth 1 (default))
console.log(nested.flat(2)); // [1, 2, 3, 4, 5, 6] (depth 2)
console.log(nested.flat(Infinity)); // [1, 2, 3, 4, 5, 6] (flatten all levels)

// Removes holes in sparse arrays
let sparse = [1, , 3, , 5];
console.log(sparse.flat()); // [1, 3, 5]

flatMap(callback)

Combines map and flat(1) in a single step. Maps each element, then flattens the result by one level:

let sentences = ["Hello world", "How are you"];

// map returns nested arrays
let words = sentences.map(s => s.split(" "));
console.log(words); // [["Hello", "world"], ["How", "are", "you"]]

// flatMap flattens one level
let flatWords = sentences.flatMap(s => s.split(" "));
console.log(flatWords); // ["Hello", "world", "How", "are", "you"]

Practical uses:

// Expanding items
let orders = [
{ id: 1, items: ["apple", "banana"] },
{ id: 2, items: ["cherry"] },
{ id: 3, items: ["date", "elderberry", "fig"] }
];

let allItems = orders.flatMap(order => order.items);
console.log(allItems); // ["apple", "banana", "cherry", "date", "elderberry", "fig"]

// Filter and map simultaneously (return empty array to exclude)
let numbers = [1, 2, 3, 4, 5, 6];
let doubledEvens = numbers.flatMap(n => n % 2 === 0 ? [n * 2] : []);
console.log(doubledEvens); // [4, 8, 12]

Converting: Array.from, Array.of

Array.from(iterable, mapFn)

Creates a new array from an iterable or array-like object:

// From a string
console.log(Array.from("hello")); // ["h", "e", "l", "l", "o"]

// From a Set
let set = new Set([1, 2, 3, 2, 1]);
console.log(Array.from(set)); // [1, 2, 3]

// From a Map
let map = new Map([["a", 1], ["b", 2]]);
console.log(Array.from(map)); // [["a", 1], ["b", 2]]

// From array-like objects (NodeList, arguments, etc.)
// let divs = Array.from(document.querySelectorAll("div"));

// With a mapping function (second argument)
console.log(Array.from({ length: 5 }, (_, i) => i + 1));
// [1, 2, 3, 4, 5]

console.log(Array.from({ length: 3 }, () => Math.random()));
// [0.123, 0.456, 0.789] (three random numbers)

Array.of(...items)

Creates an array from its arguments, avoiding the new Array(n) ambiguity:

console.log(Array.of(3));       // [3] (one element with value 3)
console.log(Array.of(1, 2, 3)); // [1, 2, 3]

// Compare with Array constructor:
console.log(new Array(3)); // [empty × 3] (confusing!)

Filling: fill, copyWithin

fill(value, start, end)

Fills array positions with a static value. Mutates the original:

let arr = [1, 2, 3, 4, 5];

arr.fill(0);
console.log(arr); // [0, 0, 0, 0, 0]

arr.fill(9, 2, 4);
console.log(arr); // [0, 0, 9, 9, 0]

// Common: create array of specific size with default value
let zeros = new Array(5).fill(0);
console.log(zeros); // [0, 0, 0, 0, 0]
fill with Objects Shares References
// ❌ WRONG: All elements reference the SAME object
let grid = new Array(3).fill([]);
grid[0].push("X");
console.log(grid); // [["X"], ["X"], ["X"]] (all changed!)

// ✅ CORRECT: Use Array.from to create independent objects
let grid2 = Array.from({ length: 3 }, () => []);
grid2[0].push("X");
console.log(grid2); // [["X"], [], []]

copyWithin(target, start, end)

Copies a portion of the array to another position within the same array. Mutates without changing the length:

let arr = [1, 2, 3, 4, 5];

// Copy elements at index 3-4 to index 0
arr.copyWithin(0, 3);
console.log(arr); // [4, 5, 3, 4, 5]
note

copyWithin is rarely used in everyday code but can be efficient for buffer manipulation.

Grouping: Object.groupBy, Map.groupBy (ES2024)

ES2024 introduced dedicated grouping methods:

Object.groupBy(iterable, callback)

let people = [
{ name: "Alice", department: "Engineering" },
{ name: "Bob", department: "Marketing" },
{ name: "Charlie", department: "Engineering" },
{ name: "Diana", department: "Design" }
];

let grouped = Object.groupBy(people, person => person.department);
console.log(grouped);
// {
// Engineering: [{ name: "Alice"... }, { name: "Charlie"... }],
// Marketing: [{ name: "Bob"... }],
// Design: [{ name: "Diana"... }]
// }

Map.groupBy(iterable, callback)

Returns a Map instead of a plain object, useful when keys are not strings:

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];

let grouped = Map.groupBy(numbers, n => n % 2 === 0 ? "even" : "odd");
console.log(grouped.get("even")); // [2, 4, 6, 8]
console.log(grouped.get("odd")); // [1, 3, 5, 7, 9]

These methods replace the common reduce-based grouping pattern with a cleaner, more readable API.

New Immutable Methods: toSorted, toReversed, toSpliced, with (ES2023)

ES2023 introduced non-mutating versions of several traditionally mutating methods. These return new arrays, leaving the original untouched.

toSorted(compareFn)

Non-mutating version of sort():

let numbers = [3, 1, 4, 1, 5, 9];

let sorted = numbers.toSorted((a, b) => a - b);
console.log(sorted); // [1, 1, 3, 4, 5, 9]
console.log(numbers); // [3, 1, 4, 1, 5, 9] (unchanged!)

toReversed()

Non-mutating version of reverse():

let arr = [1, 2, 3];
let reversed = arr.toReversed();
console.log(reversed); // [3, 2, 1]
console.log(arr); // [1, 2, 3] (unchanged!)

toSpliced(start, deleteCount, ...items)

Non-mutating version of splice():

let arr = ["a", "b", "c", "d", "e"];

let result = arr.toSpliced(1, 2, "X", "Y");
console.log(result); // ["a", "X", "Y", "d", "e"]
console.log(arr); // ["a", "b", "c", "d", "e"] (nchanged!)

with(index, value)

Returns a new array with one element replaced at the specified index:

let arr = ["a", "b", "c", "d"];

let updated = arr.with(1, "X");
console.log(updated); // ["a", "X", "c", "d"]
console.log(arr); // ["a", "b", "c", "d"] (unchanged!)

// Supports negative indices
let last = arr.with(-1, "Z");
console.log(last); // ["a", "b", "c", "Z"]

Mutating vs. Non-Mutating Comparison

MutatingNon-Mutating (ES2023)
sort()toSorted()
reverse()toReversed()
splice()toSpliced()
arr[i] = valuewith(i, value)
tip

The non-mutating versions are particularly valuable in React, Redux, and other frameworks where immutability is important. They eliminate the need for [...arr].sort() or arr.slice().reverse() patterns.

Chaining Array Methods: Composing Transformations

One of the most powerful aspects of array methods is their ability to be chained together. Since most non-mutating methods return arrays, you can pipe the output of one directly into the next:

let users = [
{ name: "Alice", age: 30, active: true },
{ name: "Bob", age: 17, active: true },
{ name: "Charlie", age: 35, active: false },
{ name: "Diana", age: 28, active: true },
{ name: "Eve", age: 22, active: true }
];

// Get names of active adult users, sorted alphabetically
let result = users
.filter(u => u.active) // Keep only active users
.filter(u => u.age >= 18) // Keep only adults
.map(u => u.name) // Extract names
.sort(); // Sort alphabetically

console.log(result); // ["Alice", "Diana", "Eve"]

A More Complex Chain

let orders = [
{ product: "Laptop", price: 999, quantity: 2 },
{ product: "Phone", price: 699, quantity: 1 },
{ product: "Cable", price: 9.99, quantity: 10 },
{ product: "Mouse", price: 29, quantity: 5 },
{ product: "Keyboard", price: 79, quantity: 3 }
];

let summary = orders
.map(order => ({
...order,
total: order.price * order.quantity
}))
.filter(order => order.total > 100)
.toSorted((a, b) => b.total - a.total)
.map(order => `${order.product}: $${order.total.toFixed(2)}`);

console.log(summary);
// ["Laptop: $1998.00", "Phone: $699.00", "Keyboard: $237.00", "Mouse: $145.00"]

When Chaining Gets Too Long

If a chain becomes hard to read, break it into named intermediate steps:

// ✅ Sometimes intermediate variables improve readability
let activeUsers = users.filter(u => u.active);
let adults = activeUsers.filter(u => u.age >= 18);
let names = adults.map(u => u.name);
let sorted = names.sort();

Performance Considerations and When to Use Which Method

Choosing the Right Method

GoalMethodReturns
Transform every elementmapNew array
Keep elements matching conditionfilterNew array
Find first matching elementfindSingle element or undefined
Find index of first matchfindIndexIndex or -1
Check if value existsincludesBoolean
Check if any element passessomeBoolean
Check if all elements passeveryBoolean
Accumulate into single valuereduceAny type
Execute side effectsforEachundefined
Add to endpushNew length
Remove from endpopRemoved element
Sort in placesortSame array (mutated)
Sort without mutatingtoSortedNew array

Performance Tips

Early termination: find, findIndex, some, and every stop as soon as they get a result. Use them instead of filter when you only need the first match or a boolean answer:

let numbers = [1, 2, 3, 4, 5, ...new Array(1000000).fill(6)];

// ❌ Slow: filter processes ALL elements, then checks length
let hasEven = numbers.filter(n => n % 2 === 0).length > 0;

// ✅ Fast: some stops at the first even number (index 1)
let hasEven2 = numbers.some(n => n % 2 === 0);

Avoid multiple passes when one will do:

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// ❌ Two passes through the array
let result = numbers
.filter(n => n % 2 === 0)
.map(n => n * 2);

// ✅ Single pass with reduce
let result2 = numbers.reduce((acc, n) => {
if (n % 2 === 0) acc.push(n * 2);
return acc;
}, []);

// ✅ Single pass with flatMap
let result3 = numbers.flatMap(n => n % 2 === 0 ? [n * 2] : []);

For small to medium arrays (hundreds to low thousands of elements), the readability of chaining filter then map far outweighs the performance difference. The reduce optimization matters only for very large arrays or performance-critical code paths.

Mutating vs. creating new arrays:

Mutating methods (sort, reverse, splice, push, pop) are slightly faster because they do not allocate new arrays. Non-mutating methods (toSorted, toReversed, map, filter) allocate new memory. For most applications, this difference is negligible. Prefer immutability for code clarity unless profiling shows it is a bottleneck.

Summary

  • splice mutates the array by adding, removing, or replacing elements. slice extracts a portion without mutating. concat merges arrays into a new one.
  • indexOf, includes search for values with strict equality. includes handles NaN correctly.
  • find and findIndex search using a callback function, essential for finding objects. findLast and findLastIndex (ES2023) search from the end.
  • map transforms every element into a new array. Always return a value from the callback.
  • filter selects elements matching a condition into a new array.
  • reduce accumulates all elements into a single value of any type. Always provide an initial value.
  • every checks if all elements pass a test. some checks if at least one passes. Both short-circuit.
  • forEach executes side effects for each element. Cannot be stopped with break.
  • sort mutates the array and defaults to string comparison. Always provide a numeric comparator for numbers.
  • flat flattens nested arrays. flatMap combines mapping and flattening.
  • Array.from converts iterables and array-likes to arrays. Array.of creates arrays from arguments.
  • fill sets elements to a static value. Beware of shared references with objects.
  • Object.groupBy and Map.groupBy (ES2024) provide clean grouping syntax.
  • ES2023 immutable methods (toSorted, toReversed, toSpliced, with) return new arrays instead of mutating, ideal for frameworks requiring immutability.
  • Chain methods for expressive data pipelines. Break long chains into named variables when readability suffers.
  • For performance, use early-terminating methods (find, some, every) when possible, and prefer single-pass operations for very large arrays.

Table of Contents