Skip to content
JavaScript js arrays 4 min read

Adding & Removing Elements

Arrays in JavaScript are dynamic, so you can grow or shrink them at any time without declaring a fixed size. The language gives you a small set of built-in methods for adding and removing elements, each targeting a specific position: the end, the start, or an arbitrary index. Knowing which method touches which end—and that most of them mutate the original array—is essential to writing predictable code.

Working at the end: push and pop

The end of an array is the cheapest place to add or remove items, because no other elements have to shift position. push() appends one or more elements to the end and returns the array’s new length. pop() removes the last element and returns it (or undefined if the array is empty).

const stack = ["a", "b"];

const newLength = stack.push("c", "d");
console.log(newLength); // 4
console.log(stack);     // ["a", "b", "c", "d"]

const removed = stack.pop();
console.log(removed);   // "d"
console.log(stack);     // ["a", "b", "c"]

Output:

4
[ 'a', 'b', 'c', 'd' ]
'd'
[ 'a', 'b', 'c' ]

Together, push and pop make an array behave like a stack (last-in, first-out), which is handy for undo histories, parser state, and recursion-free traversals.

Working at the start: shift and unshift

unshift() adds one or more elements to the beginning of the array and returns the new length, while shift() removes the first element and returns it. Because every remaining element must be re-indexed, these operations are O(n)—noticeably slower than push/pop on large arrays.

const queue = ["b", "c"];

queue.unshift("a");
console.log(queue); // ["a", "b", "c"]

const first = queue.shift();
console.log(first); // "a"
console.log(queue); // ["b", "c"]

Output:

[ 'a', 'b', 'c' ]
'a'
[ 'b', 'c' ]

Pairing push with shift gives you a queue (first-in, first-out). For high-throughput queues, a real linked-list or ring-buffer structure avoids the re-indexing cost.

Inserting and removing anywhere: splice

splice() is the Swiss Army knife: it can remove, replace, and insert elements at any index, all in one call. Its signature is:

array.splice(start, deleteCount, item1, item2, ...)
  • start — the index to begin changing (negative counts from the end).
  • deleteCount — how many elements to remove (omit to remove everything from start onward; use 0 to insert without removing).
  • item1, item2, ... — optional elements to insert at start.

splice() returns an array of the removed elements and mutates the original in place.

const colors = ["red", "green", "blue", "yellow"];

// Remove 2 elements starting at index 1
const removed = colors.splice(1, 2);
console.log(removed); // ["green", "blue"]
console.log(colors);  // ["red", "yellow"]

// Insert without removing (deleteCount = 0)
colors.splice(1, 0, "orange", "purple");
console.log(colors);  // ["red", "orange", "purple", "yellow"]

// Replace one element with another
colors.splice(0, 1, "crimson");
console.log(colors);  // ["crimson", "orange", "purple", "yellow"]

Output:

[ 'green', 'blue' ]
[ 'red', 'yellow' ]
[ 'red', 'orange', 'purple', 'yellow' ]
[ 'crimson', 'orange', 'purple', 'yellow' ]

Tip: Don’t confuse splice() with slice(). slice() is non-mutating and returns a shallow copy of a portion of the array; splice() mutates and returns what it removed. See the dedicated slice vs splice page below.

Mutation matters

Every method on this page changes the array in place rather than returning a new one. If something else holds a reference to that array—a piece of React state, a cached value, a function argument—it sees the change too, which can cause subtle bugs.

const original = [1, 2, 3];
const alias = original;

original.push(4);
console.log(alias); // [1, 2, 3, 4] — the alias changed too

Output:

[ 1, 2, 3, 4 ]

When you need an unchanged copy, build a new array with the spread operator or non-mutating alternatives such as slice(), concat(), or the newer toSpliced() (ES2023), which works like splice() but returns a new array.

const base = [1, 2, 3];
const added = [...base, 4];          // add without mutating
const cut = base.toSpliced(0, 1);    // remove without mutating

console.log(base);  // [1, 2, 3] — untouched
console.log(added); // [1, 2, 3, 4]
console.log(cut);   // [2, 3]

Output:

[ 1, 2, 3 ]
[ 1, 2, 3, 4 ]
[ 2, 3 ]

Method comparison

MethodPositionWhat it doesReturnsMutates?
push()EndAdds element(s)New lengthYes
pop()EndRemoves one elementRemoved elementYes
unshift()StartAdds element(s)New lengthYes
shift()StartRemoves one elementRemoved elementYes
splice()Any indexInserts / removes / replacesArray of removed itemsYes
toSpliced()Any indexLike spliceNew arrayNo

Best Practices

  • Prefer push/pop over shift/unshift in hot paths—operations at the end are O(1), while operations at the start are O(n).
  • Use splice(index, 1) to remove a single element by position; combine with indexOf() to remove by value.
  • Reach for toSpliced(), spread, or slice() when callers or framework state must not see in-place mutation.
  • Remember splice() returns the removed elements, not the modified array—don’t chain off its return value expecting the whole array.
  • Quote and check deleteCount: passing 0 inserts, omitting it removes everything from start to the end.
  • Avoid delete array[i]—it leaves a hole (undefined) and does not shorten the array; use splice() instead.
Last updated June 1, 2026
Was this helpful?