Array Methods

Learn the essential array methods for adding, removing, searching, transforming, and iterating over arrays, and understand clearly which methods mutate the original array and which return a new one.

JavaScript arrays come with a large built-in toolkit of methods for nearly every operation you'd want to perform: adding or removing elements, searching for a value, transforming every element, filtering down to a subset, or combining an array into a single result. The single most important distinction to internalize is which methods mutate (change) the original array in place, and which methods are non-mutating and instead return a brand-new array or value.

Mutating vs non-mutating: the core distinction

A mutating method changes the original array directly — the same array, in memory, is modified, and any other variable referencing that array will see the change too. A non-mutating method leaves the original array completely untouched and instead returns a new array or value.

This distinction matters enormously in real code, especially in frameworks like React that rely on detecting whether an array "changed" by comparing references — mutating the original array in place can cause bugs that are very hard to track down, because the framework may not notice the array's contents changed at all.

Adding and removing elements (mutating)

push(item) adds one or more items to the end of the array and returns the new length. pop() removes and returns the last item. unshift(item) adds items to the beginning, and shift() removes and returns the first item. All four of these mutate the original array directly.

splice(start, deleteCount, ...items) is the most flexible (and most mutating) method: it can remove elements, insert elements, or both, at any position, and it returns an array of the removed elements.

Extracting and combining (non-mutating)

slice(start, end) returns a new array containing a shallow copy of a portion of the original, from start up to (but not including) end, without changing the original at all — this is one of the most common ways to safely copy or extract part of an array.

concat(otherArray) returns a new array formed by combining the original with one or more other arrays or values, again without mutating anything. join(separator) converts the array into a single string, joining elements with the given separator (default ",").

Searching (non-mutating)

includes(value) returns true or false for whether a value exists in the array. indexOf(value) returns the first matching index, or -1 if not found. find(callback) returns the first element that satisfies a test function, or undefined if none match. findIndex(callback) does the same but returns the index instead of the element.

some(callback) returns true if at least one element passes the test function; every(callback) returns true only if all elements pass it. None of these searching methods mutate the array.

Transforming (non-mutating)

map(callback) returns a new array where every element is the result of calling the callback on the corresponding original element — the array stays the same length. filter(callback) returns a new array containing only the elements for which the callback returns true — the array may get shorter.

reduce(callback, initialValue) boils an entire array down to a single accumulated value (a sum, an object, another array — anything), by calling the callback once per element and carrying forward an accumulator. forEach(callback) simply runs a callback once per element, for side effects, and always returns undefined — it is not used to build a new array.

Reordering and flattening

sort(compareFn) sorts the array in place (mutating!) — without a compare function it sorts elements as strings by default, which famously sorts numbers incorrectly (e.g., 10 comes before 2). reverse() also mutates, reversing the array's order in place.

flat(depth) returns a new, non-mutating array with nested sub-arrays flattened up to the given depth (default 1). flatMap(callback) is equivalent to calling map() followed by flat(1) in a single, slightly more efficient step.

• Mutating: push, pop, shift, unshift, splice, sort, reverse • Non-mutating: slice, concat, join, includes, indexOf, find, findIndex, some, every, map, filter, reduce, forEach, flat, flatMap

Common array method signatures

map, filter, find, some, every, and forEach all receive (element, index, array) as callback arguments — index and array are optional but available. reduce's callback receives (accumulator, element, index, array), and the initialValue argument sets what the accumulator starts as. Always pass a compare function to sort() when sorting numbers: arr.sort((a, b) => a - b).

arr.push(item1, item2);         // mutates — adds to end, returns new length
arr.pop();                      // mutates — removes & returns last item
arr.slice(start, end);          // non-mutating — returns a new sub-array
arr.splice(start, deleteCount, ...items); // mutates — remove/insert
arr.map((item, index, array) => newItem);   // non-mutating
arr.filter((item, index, array) => boolean); // non-mutating
arr.reduce((accumulator, item, index, array) => newAccumulator, initialValue);

Code Examples

push() adds "deploy code" to the end of the array, mutating tasks directly. pop() then removes and returns the last item — here, the just-added "deploy code" — leaving tasks with two items again. Expected result: [ 'write code', 'test code', 'deploy code' ] deploy code [ 'write code', 'test code' ] tasks.push("deploy code"); mutates the original tasks array in place.

map(), filter(), and reduce() are all non-mutating: each one reads from prices and produces a new result (a new array, a new array, and a single number respectively), but prices itself is never altered — the final log proves it's still the original four values. Expected result: [ 17.991, 4.95, 37.8, 8.991 ] [ 19.99, 5.5, 9.99 ] 77.48 [ 19.99, 5.5, 42, 9.99 ] prices.reduce((sum, price) => sum + price, 0); accumulates a single total, starting the accumulator at 0.

find() returns the first user whose age is 30 or over. The chained filter().map().sort() first keeps only users under 30, then extracts just their names, then sorts those names alphabetically — each step returns a new array, so the chain works left to right without mutating users. flat(2) flattens two levels deep, fully unwrapping the doubly-nested [4, 5]. Expected result: { name: 'Grace', age: 34 } [ 'Ada', 'Linus' ] [ 1, 2, 3, 4, 5 ] Chaining works because filter and map each return a fresh array, which the next method in the chain can immediately call another method on.

users.filter(u => u.age < 30).map(u => u.name).sort(); chains three non-mutating methods together.

const tasks = ["write code", "test code"];
tasks.push("deploy code");
console.log(tasks);

const removed = tasks.pop();
console.log(removed);
console.log(tasks);
const prices = [19.99, 5.5, 42.0, 9.99];

const discounted = prices.map(price => price * 0.9);
const affordable = prices.filter(price => price < 20);
const total = prices.reduce((sum, price) => sum + price, 0);

console.log(discounted);
console.log(affordable);
console.log(total);
console.log(prices); // original array is unchanged
const users = [
  { name: "Ada", age: 28 },
  { name: "Grace", age: 34 },
  { name: "Linus", age: 25 }
];

const adult = users.find(u => u.age >= 30);
console.log(adult);

const names = users
  .filter(u => u.age < 30)
  .map(u => u.name)
  .sort();
console.log(names);

const nested = [[1, 2], [3, [4, 5]]];
console.log(nested.flat(2));

Mutating vs non-mutating methods

When in doubt, check the documentation: methods that reorder or resize the array in place mutate it, while methods that compute and hand back a result generally do not.

MUTATING (changes original array)      NON-MUTATING (returns a new value)
──────────────────────────────────    ────────────────────────────────────
push()      add to end                 slice()      copy a portion
pop()       remove from end            concat()     combine arrays
shift()     remove from start          map()        transform each item
unshift()   add to start               filter()     keep matching items
splice()    insert/remove anywhere     reduce()     fold into one value
sort()      reorder in place           find()       first match (or none)
reverse()   flip order in place        forEach()    run side effects only

Important Rules

push, pop, shift, unshift, splice, sort, and reverse all mutate the original array.

slice, concat, join, map, filter, reduce, find, findIndex, includes, indexOf, some, every, flat, and flatMap all leave the original array unchanged.

map always returns an array of the same length as the original; filter may return a shorter array.

forEach returns undefined and is used purely for side effects — never chain another array method after it expecting a useful result.

sort() without a compare function sorts elements as strings; always pass (a, b) => a - b for correct numeric sorting.

Where this shows up

map/filter/reduce are the backbone of nearly all modern data processing in JavaScript — rendering a list of items into UI components (map), showing only search results that match a query (filter), and computing totals or summaries from raw data (reduce). This trio, used together, replaces most manual for loops in idiomatic modern JavaScript.

const nums = [10, 2, 33, 4]; nums.sort(); console.log(nums); // [10, 2, 33, 4] → [10, 2, 33, 4] sorted as strings: [10, 2, 33, 4] const nums = [10, 2, 33, 4]; nums.sort((a, b) => a - b); console.log(nums); // [2, 4, 10, 33]

Without a compare function, sort() converts elements to strings and compares them character by character, so "10" sorts before "2" because "1" < "2". Passing (a, b) => a - b forces a proper numeric comparison.

[Multiple Choice] Which of the following array methods does NOT mutate the original array? A. push B. splice C. map D. sort

C. map — map() always returns a brand-new array and leaves the original completely unchanged. push, splice, and sort all modify the original array in place.

[True / False] arr.slice(1, 3) removes elements at indexes 1 and 2 from the original array and returns them. A. True B. False

B. False — False. slice() does not modify the original array at all — it returns a new array containing a copy of the requested portion. splice() is the method that removes elements from the original.

Given const cart = [{ name: "Book", price: 15 }, { name: "Pen", price: 2 }, { name: "Laptop", price: 900 }], write code that produces the total price of all items, and a separate array of just the item names for items priced under 100.

const cart = [ { name: "Book", price: 15 }, { name: "Pen", price: 2 }, { name: "Laptop", price: 900 } ]; const total = cart.reduce((sum, item) => sum + item.price, 0); const cheapNames = cart.filter(item => item.price < 100).map(item => item.name); console.log(total); // 917 console.log(cheapNames); // ["Book", "Pen"]

Use reduce for the total, and filter followed by map for the names.