Array methods

An array is more than a place to park values. It ships with a deep toolbox of methods, and knowing which one to reach for is most of the skill. There are a lot of them, so this chapter sorts them into groups: methods that add or remove items, methods that search, methods that iterate, and methods that transform. Learn the shape of each group and the individual methods stop feeling like trivia.

Add/remove items

You already met four methods that work at the two ends of an array:

  • arr.push(...items) — appends items to the end.
  • arr.pop() — removes and returns the last item.
  • arr.shift() — removes and returns the first item.
  • arr.unshift(...items) — prepends items to the front.

Those cover the ends. What about the middle, or copying a slice, or gluing arrays together? That’s what the next few methods are for.

splice

Say you want to delete an element. Arrays are objects under the hood, so your first instinct might be the delete operator:

let arr = ["save", "and", "exit"];

delete arr[1]; // remove "and"

alert( arr[1] ); // undefined

// now arr = ["save",  , "exit"];
alert( arr.length ); // 3

The value is gone, but the array still reports a length of 3. There’s a hole where "and" used to be.

That’s exactly what delete obj.key is designed to do: erase the value at a key and nothing else. Perfect for plain objects. But with an array you almost always want the neighbors to slide over and fill the gap, leaving a genuinely shorter array. delete won’t do that, so you need a purpose-built method.

The arr.splice method is the multitool of arrays. One method inserts, removes, and replaces. Its signature:

arr.splice(start[, deleteCount, elem1, ..., elemN])

It edits arr in place, starting at index start: it removes deleteCount elements, then inserts elem1, ..., elemN where they used to be. It returns an array of whatever it removed.

Examples make this click. Start with a plain deletion:

let arr = ["draft", "quick", "notes"];

arr.splice(1, 1); // from index 1 remove 1 element

alert( arr ); // ["draft", "notes"]

Starting at index 1, it pulled out 1 element. The array closed the gap on its own.

before“draft”“quick”“notes”
index01 ← removed2
after“draft”“notes”
splice(1, 1) removes one element at index 1; the rest shift left to close the hole.

Next, remove three elements and drop two new ones in their place:

let arr = ["we", "ship", "code", "every", "day"];

// remove 3 first elements and replace them with another
arr.splice(0, 3, "Teams", "meet");

alert( arr ) // now ["Teams", "meet", "every", "day"]

Notice deleteCount and the insert count don’t have to match. You can swap three elements for two, or two for five. The length adjusts to fit.

splice hands back the elements it removed, which is easy to forget:

let arr = ["we", "ship", "code", "every", "day"];

// remove 2 first elements
let removed = arr.splice(0, 2);

alert( removed ); // "we", "ship" <-- array of removed elements

You can also insert without removing anything. Set deleteCount to 0 and list the elements to slot in:

let arr = ["read", "the", "manual"];

// from index 2
// delete 0
// then insert "full" and "printed"
arr.splice(2, 0, "full", "printed");

alert( arr ); // "read", "the", "full", "printed", "manual"

Dial in the three arguments and watch splice insert, remove, and replace all from one call. The array on the left resets each run, so you can experiment freely:

interactivesplice explorer

slice

Despite the near-identical name, arr.slice is far tamer than arr.splice. Its job is copying, and it never touches the original.

arr.slice([start], [end])

It returns a new array holding the items from index start up to (but not including) end. Both bounds may be negative, in which case they count from the end. This mirrors the string method str.slice, except you get a subarray instead of a substring.

let arr = ["p", "l", "a", "y"];

alert( arr.slice(1, 3) ); // l,a (copy from 1 to 3)

alert( arr.slice(-2) ); // a,y (copy from -2 till the end)

Called with no arguments, arr.slice() copies the whole array. That’s a common trick when you want a throwaway copy to transform without disturbing the source:

let original = [1, 2, 3];
let copy = original.slice();
copy.push(4);
alert( original ); // 1,2,3  (unchanged)

concat

The arr.concat method builds a new array from the current one plus any number of extra arrays or values.

arr.concat(arg1, arg2...)

Every argument is either an array or a standalone value. The result contains all of arr’s items, followed by arg1, then arg2, and so on. When an argument is an array, its elements are copied over. When it’s any other value, that value itself is appended.

let arr = [1, 2];

// create an array from: arr and [3,4]
alert( arr.concat([3, 4]) ); // 1,2,3,4

// create an array from: arr and [3,4] and [5,6]
alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6

// create an array from: arr and [3,4], then add values 5 and 6
alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6

The “spread the elements” behavior only applies to real arrays. An object that merely looks like an array gets appended whole:

let arr = [1, 2];

let arrayLike = {
  0: "something",
  length: 1
};

alert( arr.concat(arrayLike) ); // 1,2,[object Object]

There’s an escape hatch. If an array-like object carries the special Symbol.isConcatSpreadable property set to a truthy value, concat treats it as an array and spreads its indexed elements:

let arr = [1, 2];

let arrayLike = {
  0: "something",
  1: "else",
  [Symbol.isConcatSpreadable]: true,
  length: 2
};

alert( arr.concat(arrayLike) ); // 1,2,something,else

Iterate: forEach

The arr.forEach method runs a function once for every element. It’s the array-native way to loop when you don’t need to build a result.

arr.forEach(function(item, index, array) {
  // ... do something with an item
});

The callback receives three arguments: the current item, its index, and the whole array. Here it alerts each element:

// for each element call alert
["Maya", "Raj", "Lena"].forEach(alert);

And a version that uses the position too:

["Maya", "Raj", "Lena"].forEach((item, index, array) => {
  alert(`${item} is at index ${index} in ${array}`);
});

Whatever the callback returns is discarded. forEach produces no value of its own — it exists purely for side effects.

Searching in array

Now for the methods that hunt through an array for something.

indexOf/lastIndexOf and includes

arr.indexOf and arr.includes mirror their string namesakes, but they compare whole items instead of characters:

  • arr.indexOf(item, from) — searches for item from index from, returning the index where it’s found, or -1 if it isn’t.
  • arr.includes(item, from) — searches from index from, returning true or false.

In practice you usually pass just the item; the search then starts at the beginning.

let arr = [1, 0, false];

alert( arr.indexOf(0) ); // 1
alert( arr.indexOf(false) ); // 2
alert( arr.indexOf(null) ); // -1

alert( arr.includes(1) ); // true

The comparison uses strict equality ===. That’s why searching for false lands on the boolean false and never on 0 — the two aren’t strictly equal. When you only care whether an item exists and don’t need its position, reach for arr.includes; it reads better than indexOf(...) !== -1.

arr.lastIndexOf works like indexOf but scans from right to left:

let fruits = ['Pear', 'Plum', 'Pear']

alert( fruits.indexOf('Pear') ); // 0 (first Pear)
alert( fruits.lastIndexOf('Pear') ); // 2 (last Pear)

find and findIndex/findLastIndex

When your array holds objects, indexOf can’t help — you rarely have the exact object reference to search for. What you have is a condition. That’s the job of arr.find.

let result = arr.find(function(item, index, array) {
  // if true is returned, item is returned and iteration is stopped
  // for falsy scenario returns undefined
});

The callback runs for each element in turn, receiving:

  • item — the element,
  • index — its position,
  • array — the array itself.

The moment the callback returns a truthy value, find stops and hands back that item. If nothing matches, it returns undefined.

let users = [
  {id: 1, name: "Maya"},
  {id: 2, name: "Raj"},
  {id: 3, name: "Lena"}
];

let user = users.find(item => item.id == 1);

alert(user.name); // Maya

Arrays of objects show up constantly, which makes find one of the workhorses. Note we pass item => item.id == 1, using only the first parameter — the index and array arguments exist but are seldom needed.

arr.findIndex shares the same signature but returns the matching index rather than the element, or -1 when nothing matches. arr.findLastIndex does the same scan from right to left, the way lastIndexOf relates to indexOf:

let users = [
  {id: 1, name: "Maya"},
  {id: 2, name: "Raj"},
  {id: 3, name: "Lena"},
  {id: 4, name: "Maya"}
];

// Find the index of the first Maya
alert(users.findIndex(user => user.name == 'Maya')); // 0

// Find the index of the last Maya
alert(users.findLastIndex(user => user.name == 'Maya')); // 3

filter

find returns a single element — the first match, then it stops. When you want every match, use arr.filter.

The signature looks like find, but instead of one element you get a new array of all the elements that passed:

let results = arr.filter(function(item, index, array) {
  // if true item is pushed to results and the iteration continues
  // returns empty array if nothing found
});
let users = [
  {id: 1, name: "Maya"},
  {id: 2, name: "Raj"},
  {id: 3, name: "Lena"}
];

// returns array of the first two users
let someUsers = users.filter(item => item.id < 3);

alert(someUsers.length); // 2
sourceid:1id:2id:3
find(id<3)id:1← first match, stop
filter(id<3)id:1id:2← all matches
find returns the first passing element; filter collects every passing element into a new array.

Slide the age threshold and watch filter rebuild its result array on the fly — every user whose age clears the bar survives, the rest are dropped:

interactivefilter by a live condition

Transform an array

Now the methods that reshape or reorder an array.

map

arr.map is among the most-used methods in all of JavaScript. It calls your function on each element and collects the return values into a new array of the same length.

let result = arr.map(function(item, index, array) {
  // returns the new value instead of item
});

Here each string becomes its length:

let lengths = ["orbit", "cluster", "moon"].map(item => item.length);
alert(lengths); // 5,7,4
“orbit”↓ .length5
“cluster”↓ .length7
“moon”↓ .length4
map applies a function element-by-element and returns a new array of the results — same length, transformed values.

sort(fn)

arr.sort() reorders the array in place. It returns the array too, but that return value is usually ignored since arr itself changes.

let arr = [ 1, 2, 15 ];

// the method reorders the content of arr
arr.sort();

alert( arr );  // 1, 15, 2

Look closely at the output: 1, 15, 2. That’s wrong for numbers. What happened?

By default, sort compares elements as strings.

Every element is converted to a string, then compared lexicographically — character by character. Under that rule "15" comes before "2", because the first character "1" is less than "2". The array never looked at the numeric values at all.

To impose your own order, pass a comparison function. It receives two elements and must return a number that says which comes first:

function compare(a, b) {
  if (a > b) return 1; // if the first value is greater than the second
  if (a == b) return 0; // if values are equal
  if (a < b) return -1; // if the first value is less than the second
}

Plug that logic in and numbers sort numerically:

function compareNumeric(a, b) {
  if (a > b) return 1;
  if (a == b) return 0;
  if (a < b) return -1;
}

let arr = [ 1, 2, 15 ];

arr.sort(compareNumeric);

alert(arr);  // 1, 2, 15

Step back and consider why the design works this way. An array can hold anything — numbers, strings, objects, a mix. sort can’t possibly know how you’d rank arbitrary values, so it delegates that one decision to your function and keeps the rest. It handles walking the array, comparing pairs, and shuffling elements into place; you only supply the “which of these two comes first” rule.

Internally, arr.sort(fn) runs a general-purpose sorting algorithm — engines typically use an optimized quicksort or Timsort. You don’t manage any of that. If you’re curious which pairs it compares, log them:

[3, -1, 12, 4, 0, 7].sort(function(a, b) {
  alert( a + " <> " + b );
  return a - b;
});

A given element may be compared against several others as the algorithm runs, though it works to keep the number of comparisons small.

Here’s the string-vs-number trap in one place. The same array, sorted three ways — the default (string) order, an ascending comparator, and a descending one. Notice how the default drops 15 before 2:

interactivesort: default vs comparator

reverse

arr.reverse flips the element order in place.

let arr = [1, 2, 3, 4, 5];
arr.reverse();

alert( arr ); // 5,4,3,2,1

It returns the reversed arr as well.

split and join

A common real-world need: a user types a comma-separated list of recipients — Maya, Raj, Lena — and you’d much rather work with an array of names than one long string.

str.split(delim) does that. It breaks a string into an array, cutting at every occurrence of the delimiter delim. Here we split on a comma-plus-space:

let names = 'Maya, Raj, Lena';

let arr = names.split(', ');

for (let name of arr) {
  alert( `A message to ${name}.` ); // A message to Maya  (and other names)
}

split takes an optional second argument: a numeric cap on how many elements to return. Anything beyond the limit is dropped. It’s rarely used, but it exists:

let arr = 'Maya, Raj, Lena, Priya'.split(', ', 2);

alert(arr); // Maya, Raj

arr.join(glue) is the inverse. It stitches every array item into one string, placing glue between each pair:

let arr = ['Maya', 'Raj', 'Lena'];

let str = arr.join(';'); // glue the array into a string using ;

alert( str ); // Maya;Raj;Lena

reduce/reduceRight

To just iterate, you have forEach, for, and for..of. To iterate and produce a new element for each, you have map. arr.reduce and arr.reduceRight are the next step up: they fold the whole array down to a single value.

let value = arr.reduce(function(accumulator, item, index, array) {
  // ...
}, [initial]);

The function runs across every element, and each call’s return value is carried into the next call as the accumulator. The arguments:

  • accumulator — the result of the previous call; on the first call it equals initial (when you provide one).
  • item — the current element.
  • index — its position.
  • array — the array.

So accumulator is a running total that threads through every call and becomes the final result of reduce.

An example is worth more than the description. Summing an array in one line:

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

let result = arr.reduce((sum, current) => sum + current, 0);

alert(result); // 15

The callback only uses two of the four parameters, which is typical. Walking through it:

  1. First call: sum is the initial value 0, current is the first element 1. Returns 1.
  2. Second call: sum = 1, add the second element 2, return 3.
  3. Third call: sum = 3, add 3, return 6 — and so on.

The result of each call becomes the sum of the next:

0+1→1+2→3+3→6+4→10+5→15
reduce threads the accumulator through each call; every result becomes the next call's sum.

The same run as a table, one row per call:

sum current result
the first call 0 1 1
the second call 1 2 3
the third call 3 3 6
the fourth call 6 4 10
the fifth call 10 5 15

The power of reduce is that swapping the folding rule changes the whole computation while the shape of the code stays the same. Pick a reducer below and step through each call to see the accumulator thread along:

interactivereduce: one loop, different folds

You can leave out the initial value:

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

// removed initial value from reduce (no 0)
let result = arr.reduce((sum, current) => sum + current);

alert( result ); // 15

Same answer. Without an initial value, reduce uses the array’s first element as the starting accumulator and begins iterating from the second element. The table above simply loses its first row.

That shortcut has a sharp edge, though. On an empty array with no initial value, there’s nothing to start from and reduce throws:

let arr = [];

// Error: Reduce of empty array with no initial value
// if the initial value existed, reduce would return it for the empty arr.
arr.reduce((sum, current) => sum + current);

Passing an initial value sidesteps this entirely — an empty array just returns that initial value. Get in the habit of supplying it.

arr.reduceRight behaves identically but walks the array from right to left.

Array.isArray

Arrays aren’t a distinct language type; they’re built on objects. So typeof can’t tell them apart from plain objects:

alert(typeof {}); // object
alert(typeof []); // object (same)

Because arrays come up so often, there’s a dedicated check: Array.isArray(value) returns true for arrays and false for everything else.

alert(Array.isArray({})); // false

alert(Array.isArray([])); // true

Most methods support “thisArg”

Nearly every array method that takes a callback — find, filter, map, and friends, with sort being the notable exception — accepts an optional final argument, thisArg. It’s rarely needed, so the earlier sections skipped it, but here’s the full picture:

arr.find(func, thisArg);
arr.filter(func, thisArg);
arr.map(func, thisArg);
// ...
// thisArg is the optional last argument

Whatever you pass as thisArg becomes this inside func. That matters when your callback is a method that relies on this:

let ride = {
  minHeight: 120,
  maxHeight: 195,
  canBoard(rider) {
    return rider.height >= this.minHeight && rider.height <= this.maxHeight;
  }
};

let riders = [
  {height: 110},
  {height: 140},
  {height: 175},
  {height: 200}
];

// find riders, for who ride.canBoard returns true
let allowed = riders.filter(ride.canBoard, ride);

alert(allowed.length); // 2
alert(allowed[0].height); // 140
alert(allowed[1].height); // 175

Drop the second argument — riders.filter(ride.canBoard) — and canBoard runs as a detached function with this set to undefined, so this.minHeight throws immediately.

In modern code, most people skip thisArg and write an arrow function instead: riders.filter(rider => ride.canBoard(rider)) does the same thing and reads more plainly, since the arrow keeps ride as the receiver.

Summary

A cheat sheet of array methods:

  • To add/remove elements:

    • push(...items) — adds items to the end,
    • pop() — extracts an item from the end,
    • shift() — extracts an item from the beginning,
    • unshift(...items) — adds items to the beginning.
    • splice(pos, deleteCount, ...items) — at index pos deletes deleteCount elements and inserts items.
    • slice(start, end) — creates a new array, copies elements from index start till end (not inclusive) into it.
    • concat(...items) — returns a new array: copies all members of the current one and adds items to it. If any of items is an array, then its elements are taken.
  • To search among elements:

    • indexOf/lastIndexOf(item, pos) — look for item starting from position pos, and return the index or -1 if not found.
    • includes(value) — returns true if the array has value, otherwise false.
    • find/filter(func) — filter elements through the function, return first/all values that make it return true.
    • findIndex is like find, but returns the index instead of a value.
  • To iterate over elements:

    • forEach(func) — calls func for every element, does not return anything.
  • To transform the array:

    • map(func) — creates a new array from results of calling func for every element.
    • sort(func) — sorts the array in-place, then returns it.
    • reverse() — reverses the array in-place, then returns it.
    • split/join — convert a string to array and back.
    • reduce/reduceRight(func, initial) — calculate a single value over the array by calling func for each element and passing an intermediate result between the calls.
  • Additionally:

    • Array.isArray(value) checks value for being an array, if so returns true, otherwise false.

Keep in mind that sort, reverse, and splice modify the array itself.

These methods cover the overwhelming majority of everyday work. A few more are worth knowing about:

  • arr.some(fn) / arr.every(fn) test the array. fn runs on each element, like in map. some returns true if any call is truthy; every returns true only if all calls are.

    They short-circuit like the || and && operators: as soon as fn returns a truthy value, some returns true and stops; as soon as fn returns a falsy value, every returns false and stops.

    every is handy for comparing two arrays element by element:

    function arraysEqual(arr1, arr2) {
      return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]);
    }
    
    alert( arraysEqual([1, 2], [1, 2])); // true
some(x→x>2)1 ✕2 ✕3 ✓ stop → true
every(x→x>2)1 ✕ stop → false
some stops at the first truthy result; every stops at the first falsy result.
  • arr.fill(value, start, end) — fills the array with value from index start up to end.

  • arr.copyWithin(target, start, end) — copies the slice from start to end into itself at position target, overwriting what’s there.

  • arr.flat(depth) / arr.flatMap(fn) — build a new, flatter array out of a nested one.

The full catalog lives in the MDN Array reference.

That’s a lot of methods, and the list can feel daunting at first. It gets manageable fast. Skim the cheat sheet so you know what exists, then work the exercises to build muscle memory. After that, whenever you’re stuck on an array task, come back here, scan the list, and grab the method that fits — the examples will keep you honest on the syntax. Before long you’ll reach for the right one without thinking about it.