skip to content

Arrays and Array Methods

The Array prototype is the workhorse of everyday JavaScript, and fluency with it is what separates a candidate who writes loops from one who writes intent. This group covers the method families, the pitfalls each one hides, and the newer copying variants that make immutable updates painless.

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

explore

questions

26

In JavaScript, what is the difference between Array.prototype.slice() and Array.prototype.splice(), and what does each one return?

level: juniorimportance: must knowfreq 80%

answer

  1. one letter apart, opposite behaviour
  2. one copies a range, one edits in place
  3. check what each hands back
  4. splice returns what was removed
  5. slice(0,3) reads, splice(0,3) removes

basics

~20 s

slice() copies: it leaves the original array untouched and returns a new array holding the selected range. splice() edits in place: it removes and/or inserts elements in the original array and returns an array of the elements it removed.

solid answer

~40 s

`slice(start, end)` is a copying method. It returns a new array with the elements from `start` up to but not including `end`, supports negative indexes counted from the end, and never touches the receiver — `arr.slice()` with no arguments is the classic full shallow copy idiom. `splice(start, deleteCount, ...items)` is a mutating method: it deletes `deleteCount` elements at `start`, inserts any extra arguments there, changes `length` and reindexes the array — and its return value is the array of *removed* elements, not the array you spliced. So `list.slice(0, 3)` hands you the first three items and leaves `list` alone, while `list.splice(0, 3)` hands you the same three items but leaves `list` three elements shorter. Confusing the two is one of the most common accidental-mutation bugs in JavaScript.

code

javascript · 10 lines
javascript
const list = ['a', 'b', 'c', 'd'];

const copied = list.slice(1, 3);
console.log(copied);            // ['b', 'c']
console.log(list.length);       // 4 — original intact

const removed = list.splice(1, 2, 'X');
console.log(removed);           // ['b', 'c'] — what came out
console.log(list);              // ['a', 'X', 'd'] — mutated
console.log(list.length);       // 3

go deeper

for a junior

Be ready to state plainly that slice copies and splice mutates, and to say what each returns: a new array of the selected range versus the elements that were removed.

for a middle

Explain the argument shapes — slice takes start and end while splice takes start and deleteCount plus insertions — and demonstrate the insert idiom splice(i, 0, x) and negative-index handling.

for a senior

Show the judgment of not mutating arrays you do not own: copy at function boundaries, treat splice's return value as "what came out", and name toSpliced as the copying counterpart.

for a principal

Frame it as an API-contract decision: whether your utilities take ownership of arguments or promise not to mutate them, and how you make that contract obvious in naming, documentation and review.

## Two similar names, opposite behaviour JavaScript arrays are mutable objects, and the array methods fall into two families: **mutating** methods that change the array they are called on, and **copying** methods that leave it alone and hand back a new array. `slice` and `splice` sit on opposite sides of that line while differing by one letter, which is exactly why interviewers pair them. ## slice(start, end) — copying `slice` returns a **new** array containing the elements from index `start` up to but *not including* `end`. Both arguments are optional: `start` defaults to `0`, `end` defaults to `length`. Negative values count back from the end of the array. ```js const a = ['a', 'b', 'c', 'd']; console.log(a.slice(1, 3)); // ['b', 'c'] console.log(a.slice(-2)); // ['c', 'd'] console.log(a); // ['a','b','c','d'] — untouched const full = a.slice(); // full shallow copy ``` The copy is **shallow**: the new array holds the same element values, so for object elements both arrays point at the same objects. ## splice(start, deleteCount, ...items) — mutating `splice` edits the receiver in place. It removes `deleteCount` elements beginning at `start` and inserts any further arguments at that position, shifting the remaining elements and updating `length`. Its **return value is an array of the removed elements**. ```js const a = ['a', 'b', 'c', 'd']; const removed = a.splice(1, 2, 'X'); console.log(removed); // ['b', 'c'] <- what came out console.log(a); // ['a', 'X', 'd'] <- the array itself changed a.splice(1, 0, 'y'); // deleteCount 0 = pure insert console.log(a); // ['a', 'y', 'X', 'd'] a.splice(2); // omitting deleteCount removes to the end console.log(a); // ['a', 'y'] ``` A negative `start` counts from the end, and a `start` past `length` clamps to the end, so `arr.splice(arr.length, 0, x)` is an awkward `push`. ## Where the confusion bites The classic bug is reaching for `splice` when you only wanted to *read* a range: ```js function topThree(list) { return list.splice(0, 3); // BUG: shrinks the caller's list } ``` The function returns the right three items, so a quick test passes — but every call permanently removes three elements from the array that was passed in. `list.slice(0, 3)` is the correct call. The mirror-image mistake is expecting `splice` to hand back the *edited* array, which leads to `const updated = arr.splice(1, 1)` and then wondering why `updated` has one element in it. ## Return-value cheat sheet for the neighbours It helps to memorise the whole in-place family, because their return values are all different: `push`/`unshift` return the new `length`, `pop`/`shift` return the single removed element, `splice` returns an array of removed elements, and `reverse`/`sort`/`fill`/`copyWithin` return **the same array object** they just mutated. Meanwhile `slice`, `concat`, `map`, `filter`, `flat` and `flatMap` all return fresh arrays. ## The ES2023 copying counterpart ES2023 added `toSpliced(start, skipCount, ...items)`, the non-mutating twin of `splice`. Note the return value flips: `toSpliced` returns the **resulting** array and throws the removed elements away, whereas `splice` returns the removed elements and keeps the result in place. ```js const a = ['a', 'b', 'c', 'd']; const b = a.toSpliced(1, 2, 'X'); console.log(b); // ['a', 'X', 'd'] console.log(a); // ['a','b','c','d'] — untouched ``` ## Practical guidance If the array is shared — a function parameter, a value stored in a container someone else reads, anything you did not create in the current function — prefer the copying method and hand back a new array. Reach for `splice` when you deliberately own the array and want an in-place edit, and read its return value as "what came out", never as "the new array".

  • What does splice return if deleteCount is 0?
    An empty array. With `deleteCount` of `0`, `splice` removes nothing and only inserts the extra arguments, so the "removed elements" array it returns has no members — but the receiver is still mutated by the insertion. It is the standard idiom for inserting at an index: `arr.splice(2, 0, 'x')`.
  • How would you delete one element from an array without mutating it?
    Copy around the index: `arr.slice(0, i).concat(arr.slice(i + 1))`, or in an ES2023 runtime simply `arr.toSpliced(i, 1)`, which returns the resulting array and leaves the original alone. Avoid `delete arr[i]` — that leaves a hole and does not change `length`.
  • Is the array returned by slice() safe to mutate freely?
    The array object itself is new, so pushing, sorting or reversing it will not affect the original. But the copy is shallow: the elements are the same references, so mutating a nested object such as `copy[0].name = 'x'` is visible through the original array too.

saying these in an interview costs you the question

  • Thinking splice returns the modified array
  • Saying slice mutates because it changes indexes
  • Using splice(0, 3) to peek at the first three items
  • Believing both methods take (start, end) arguments
  • Assuming the array from slice() deep-copies nested objects

context

open as a page

In JavaScript array destructuring, when does a default value like `const [a = 5] = values` actually apply — and what do you get when the matching element is null, a hole, or missing entirely?

level: juniorimportance: must knowfreq 72%

basics

~20 s

A destructuring default fires only when the matched value is exactly undefined — a missing index, an array hole, or an explicit undefined. null, 0, empty string and NaN are real values, so they are bound as-is.

open as a page

In JavaScript, what do Array.prototype.find and Array.prototype.findIndex return when no element satisfies the predicate, and what bug does that difference commonly cause?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Array.prototype.find returns undefined when nothing matches, while findIndex returns -1. The bug is truthiness: -1 is truthy and index 0 is falsy, so testing findIndex's result directly inverts the check. Compare it against -1 instead.

open as a page

Why does [10, 9, 1].sort() return [1, 10, 9] in JavaScript, and how do you sort an array of numbers correctly?

level: juniorimportance: must knowfreq 85%

basics

~20 s

Array.prototype.sort called with no comparator converts every element to a string and compares those strings, so "10" sorts before "9". Pass a numeric comparator, arr.sort((a, b) => a - b), to order numbers by value.

open as a page

In JavaScript, what is the difference between Array.prototype.map and Array.prototype.filter, and what does each one return?

level: juniorimportance: must knowfreq 82%

basics

~20 s

Array.prototype.map builds a new array of the same length by replacing each element with whatever its callback returns. Array.prototype.filter builds a new array holding only the elements whose callback returned a truthy value. Neither changes the original array.

open as a page

What exactly does the spread syntax copy in `const copy = [...items]` or `const next = {...state}`, and what bug appears once the data has a nested level?

level: middleimportance: must knowfreq 78%

basics

~20 s

Spread produces a one-level copy: the new array or object is a fresh container, but every nested array or object inside it is the very same reference as in the original. Mutating a nested value is visible through both.

open as a page

What is the difference between Array.prototype.includes and Array.prototype.indexOf, and why can indexOf never find NaN inside an array?

level: middleimportance: must knowfreq 62%

basics

~20 s

Array.prototype.includes returns a boolean and compares with SameValueZero, so it finds NaN; indexOf returns a position or -1 and compares with strict equality, and NaN === NaN is false, so it never matches. They also disagree on holes in sparse arrays.

open as a page

What must a comparator passed to Array.prototype.sort return, and what goes wrong if you write (a, b) => a > b?

level: middleimportance: must knowfreq 66%

basics

~20 s

A sort comparator must return a negative number to put a first, zero for a tie, and a positive number to put b first. Returning a boolean, as (a, b) => a > b does, gives only 1 or 0 — never negative — so the order is wrong.

open as a page

What does the second argument to Array.prototype.reduce do, and what happens if you omit it?

level: middleimportance: must knowfreq 70%

basics

~20 s

The second argument is the initial accumulator value. Supply it and the callback runs once per element starting at index 0. Omit it and the first element becomes the accumulator, iteration starts at index 1, and an empty array throws a TypeError.

open as a page

You copy an array of objects with slice(), then edit a property on one element of the copy — and the original array shows the change too. Why, and how do you get a copy that does not share nested data?

level: seniorimportance: must knowfreq 65%

basics

~20 s

Array copy methods such as slice, concat and map are shallow: they copy element references, so nested objects remain shared between the two arrays and editing one shows through both. A deep copy needs structuredClone or explicit per-level copying.

open as a page

What does Array.prototype.at(-1) return, and how does it differ from writing arr[-1]?

level: juniorimportance: should knowfreq 38%

basics

~20 s

Array.prototype.at(-1) returns the last element, because a negative argument counts back from the end. Bracket notation does not: arr[-1] is an ordinary property lookup for the key "-1", which normally yields undefined, and assigning to it adds a property without changing length.

open as a page

What are the ES2023 array methods toSorted, toReversed, toSpliced and with, and how do they differ from their older counterparts?

level: middleimportance: should knowfreq 40%

basics

~20 s

ES2023 added four copying array methods — toSorted, toReversed, toSpliced and with — that return a new array instead of editing the receiver, giving non-mutating counterparts to sort, reverse, splice and assignment to an index.

open as a page

What do JavaScript's in-place array methods such as push, pop, splice, reverse and fill actually return, and what bug follows from assuming they return a new array?

level: middleimportance: should knowfreq 55%

basics

~20 s

Mutating array methods rarely return a new array: push and unshift return the new length, pop and shift return the removed element, splice returns the removed elements, and reverse, sort, fill and copyWithin return the same array object they just changed.

open as a page

In JavaScript, what do `Array.from`, `Array.of`, `Array(n)` and array spread each produce, and which one do you use to build an array of n computed values?

level: middleimportance: should knowfreq 45%

basics

~10 s

Array(n) makes a length-n array with no elements at all, so array methods have nothing to visit. Array.from({length: n}, fn) fills those positions, Array.of(n) makes a single-element array, and spread converts anything iterable.

open as a page

In JavaScript destructuring, what does the rest element bind in `const [first, ...rest] = arr` and `const {id, ...others} = obj`, and what restrictions does the syntax place on it?

level: middleimportance: should knowfreq 60%

basics

~20 s

A rest element collects everything not matched by earlier targets into a brand-new array or object — an empty one when nothing remains, never undefined. It must be the last element in the pattern, takes no default, and copies only one level deep.

open as a page

How do Array.prototype.some and Array.prototype.every short-circuit, and what does each of them return for an empty array?

level: middleimportance: should knowfreq 50%

basics

~20 s

Array.prototype.some stops at the first element whose predicate returns truthy and yields true; every stops at the first falsy result and yields false. On an empty array some returns false and every returns true, because every is vacuously satisfied when there is nothing to violate it.

open as a page

How do you write a single comparator for Array.prototype.sort that orders employee records by department name ascending and then by salary descending?

level: middleimportance: should knowfreq 50%

basics

~20 s

Chain the per-key comparisons with the logical OR operator: (a, b) => a.dept.localeCompare(b.dept) || b.salary - a.salary. A zero from the first key is falsy, so evaluation falls through to the tiebreaker; reversing a key means swapping its two operands.

open as a page

Since ES2019, Array.prototype.sort is required to be stable. What exactly does that guarantee, and what can you build on it?

level: middleimportance: should knowfreq 36%

basics

~20 s

Stability means elements whose comparator returns zero keep the relative order they had in the input. It lets you order by a secondary key in one pass and a primary key in the next, and it makes repeated sorts of the same data reproducible.

open as a page

In JavaScript, why does ['zebra', 'Apple', 'ähre'].sort() produce ['Apple', 'zebra', 'ähre'], and how do you get human-expected alphabetical order?

level: middleimportance: should knowfreq 44%

basics

~10 s

A comparator-less sort compares strings by UTF-16 code unit, so every uppercase ASCII letter precedes every lowercase one and accented letters land after z. Use a locale-aware comparator instead: arr.sort((a, b) => a.localeCompare(b)).

open as a page

What is the difference between Array.prototype.flat and Array.prototype.flatMap, and how deep does each one flatten?

level: middleimportance: should knowfreq 45%

basics

~10 s

Array.prototype.flat flattens nested arrays to a depth of 1 by default and accepts a depth argument, including Infinity for full flattening. Array.prototype.flatMap maps each element then flattens exactly one level, with no depth option.

open as a page

Why does ['1', '7', '11'].map(parseInt) return [1, NaN, 3] instead of [1, 7, 11]?

level: middleimportance: should knowfreq 55%

basics

~20 s

Array.prototype.map calls its callback with three arguments — element, index and array — so the index is passed as parseInt's radix parameter. The calls become parseInt('1', 0), parseInt('7', 1) and parseInt('11', 2), giving 1, NaN and 3.

open as a page

A helper function receives an array parameter and calls reverse() on it before returning a slice. What goes wrong for the caller, and how should the function be written instead?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Calling an in-place method such as reverse, sort, splice or fill on an array parameter edits the caller's own array, causing changes the caller never asked for. Copy first — with slice() or an ES2023 copying method — before reordering shared input.

open as a page

A config object built as `{...defaults, ...userOptions}` loses nested defaults and, when the source was a class instance, its methods too. What does object spread actually copy, and why do both symptoms follow?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Object spread copies own enumerable properties, one level deep, later sources overwriting earlier ones per key. A nested key is replaced wholesale rather than merged, and nothing on the prototype — including class methods — comes along.

open as a page

A page keeps an array of selected user objects and tests membership with selected.includes(user). After the user list is refetched from the server, the check starts returning false even though the same person is on screen. What is happening, and how do you fix it?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Array.prototype.includes compares objects by reference, not by content. A refetch builds brand-new objects, so the stored one and the fresh one are different values even with identical fields. Search by a stable field instead, with some or find and a predicate.

open as a page

A product table sorted with (a, b) => a.price - b.price shows a few rows in obviously wrong positions on the full dataset, though a short test list sorts fine. How do you diagnose and fix it?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Some price values are not numbers, so the subtraction yields NaN, which sort treats as zero. Those rows compare equal to everything, making the comparator inconsistent and the order implementation-defined. Normalise prices to numbers and give missing values an explicit rank.

open as a page

You need to group or count an array of objects by a key using reduce. How do you design the accumulator, and what is wrong with returning { ...acc, [key]: value } from the reducer?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Seed reduce with a fresh empty object or Map, then mutate that accumulator in place and return it. Spreading the accumulator on every iteration copies every key accumulated so far, turning a linear fold into quadratic work and heavy garbage for no safety benefit.

open as a page