Command Palette

Search for a command to run...

Hectal
PHASE 5Beginner ~19 min· topic 2 of 10

Topic 5.2

Adding, Removing and Changing

In one line

push/pop work at the end of an array, unshift/shift at the start, and splice anywhere in the middle; all of them change (mutate) the original array, while methods like slice, concat, with and toSpliced leave it alone and return a new one.

Think of it like this

A stack of plates and a queue at a ticket counter. You put a clean plate on top of the stack and take the top one off: that's push and pop, working at the end of an array. In a queue, people join at the back (push) and the person at the front leaves first (shift). Someone pushing in at the front is unshift. And a shop assistant who pulls two people out of the middle of the queue and puts a new one in their place is splice.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Mutate
Change something in place, so everyone holding it sees the change.
Non-mutating
Leaves the original untouched and gives you back a new array instead.
Stack
A pile where the last thing added is the first one taken out (push + pop).
Queue
A line where the first thing added is the first one taken out (push + shift).
splice
The method that removes and/or inserts items at any position, changing the array.
slice
The method that copies part of an array into a new one, without changing the original.
Return value
What a method gives back when it finishes, for example the new length from push.

Step by step

01Work at the end

push adds, pop removes. Look at what each one returns: push gives the new length, not the array.

app.jswhole filejavascript
const stack = ["a"];
stack.push("b", "c"); // returns 3
stack.pop();          // returns "c"
// stack is now [ 'a', 'b' ]

02Work at the start

unshift adds at the front, shift removes from the front. Every other item moves to a new index.

Work at the startdiagram
Rendering diagram…

03Work in the middle with splice

Read splice(1, 2, "X") as: "go to index 1, remove 2 items, put X there". The removed items come back as an array.

app.jswhole filejavascript
const a = [10, 20, 30, 40];
const gone = a.splice(1, 2, "X");
// gone is [ 20, 30 ]
// a    is [ 10, 'X', 40 ]

04Copy instead of change

When you must keep the original, use the non-mutating twin. toSpliced takes the same arguments as splice but returns a new array; with(i, v) is a copy with one slot replaced.

app.jswhole filejavascript
const b = [10, 20, 30];
const c = b.with(1, 99);   // [ 10, 99, 30 ]
const d = b.toSpliced(0, 1); // [ 20, 30 ]
// b is still [ 10, 20, 30 ]

05Pick the right tool

End: push/pop. Start: unshift/shift. Middle: splice (or toSpliced for a copy). Replace one item: arr[i] = v (or with for a copy). Part of the array: slice. Join arrays: concat or spread (Topic 5.8).

Try it yourself

  1. 1

    Undo the last join

    In the first example, add a third button that removes the last person in the queue. Which method do you need? Predict what it returns when the queue is empty, then try it.

  2. 2

    Insert in the middle

    In the second example, before the last splice, insert "M" between "X" and "Y" with one splice call. Predict the full array first.

  3. 3

    Swap a method for its copy

    In the third example, replace original.with(0, 99) with original[0] = 99. Predict all the lines now. Which ones change, and why?

Code & diagrams

A queue at the counter New tab

Click the buttons: joining uses `push` (add at the back), serving uses `shift` (remove from the front). The console part shows what each method returns.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

3
Meera
3
Dev
[ 'Asha', 'Ravi' ]
undefined
splice: remove, insert and replace New tab

Each call changes `letters`. Only the first one keeps what `splice` returned.

Console

(nothing was printed)

Expected console output

[ 'b', 'c' ] [ 'a', 'd', 'e' ]
[ 'a', 'X', 'Y', 'd', 'e' ]
[ 'a', 'X', 'Y', 'd', 'Z' ]
[ 'a', 'X' ]
Mutating vs non-mutating ES2023+ New tab

`with` and `toSpliced` are ES2023. The original is untouched by all four copying methods. `delete` leaves a hole; `length = 0` empties the array for everyone who holds it.

Console

(nothing was printed)

Expected console output

[ 3, 1, 2 ]
[ 3, 1 ] [ 3, 1, 2, 4, 5 ] [ 99, 1, 2 ] [ 1, 2 ]
[ 'a', <empty>, 'c' ] 3
[]

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Expecting push to return the array

Write const list = [1, 2].push(3); console.log(list.length);

terminal
$ Console
── what you'll see ──
undefined

Break #2

Mixing up slice and splice

To read the first two items for a preview, write const preview = items.splice(0, 2);.

terminal
$ the page shows
── what you'll see ──
The preview is right, but the first two items have disappeared from the main list.

Myth vs fact

Myth

delete arr[i] removes an item from an array.

Fact

It removes the value and leaves a hole; length doesn't change and later items don't move. Use splice(i, 1) or toSpliced(i, 1).

Myth

slice and splice are two spellings of the same thing.

Fact

slice(start, end) copies part of the array and never changes it. splice(start, count, ...items) removes and inserts in place and returns what it removed.

Myth

shift is as fast as pop.

Fact

pop touches only the last slot. shift must move every remaining element one place to the left, so it gets slower as the array grows.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    push and pop are amortised O(1): the engine keeps spare capacity and only reallocates (usually growing by about 1.5×) when it runs out. shift/unshift/splice in the middle are O(n). V8 has tricks for shift on small arrays, but don't rely on them for big queues; use a ring buffer or two-index queue.

  • ▸

    The ES2023 change-by-copy methods (toSorted, toReversed, toSpliced, with) also exist on typed arrays (except toSpliced). with throws a RangeError for an index outside the array instead of growing it, unlike arr[i] = v.

  • ▸

    slice() with no arguments and [...arr] both make a shallow copy. slice keeps holes as holes; spread turns holes into undefined values.

  • ▸

    fill(value) puts the same value in every slot. new Array(3).fill([]) gives three references to one inner array, a classic bug; use Array.from({ length: 3 }, () => []) (Topic 5.9).

Remember this

  1. 1

    push(a, b, ...) adds one or more items to the end and returns the new length. pop() removes the last item and returns that item (or undefined if the array was empty). They are fast, because nothing else has to move. Together they make an array work as a stack (last in, first out).

  2. 2

    unshift(a, b, ...) adds items to the start and returns the new length. shift() removes the first item and returns it. These are slower on long arrays, because every other element has to move along one place to get a new index. push + shift makes a queue (first in, first out); for very large queues a different data structure is better (see Hectal's /dsa course).

  3. 3

    splice(start, deleteCount, ...newItems) is the all-in-one tool for the middle. It removes deleteCount items starting at index start, inserts any newItems in their place, and returns an array of the removed items. splice(1, 0, "x") inserts without removing; splice(2) with no count removes everything from index 2 to the end; a negative start counts from the end. Don't confuse it with slice, which only copies.

  4. 4

    A method that changes the array it's called on mutates it ("mutate" means change in place). push, pop, shift, unshift, splice, sort, reverse, fill and copyWithin all mutate. Non-mutating methods leave the original alone and return a new array: slice(start, end) (a copy of part of the array, end not included), concat(...) (joined copy), and the ES2023 "change by copy" methods with(index, value), toSpliced(...), toSorted() and toReversed().

  5. 5

    Why it matters: when two parts of your program hold the same array (Topic 5.10 explains references), mutating it changes what both of them see. Frameworks like React decide whether to redraw the screen by checking whether you gave them a new array, so mutating in place can make the screen fail to update. A good habit: mutate arrays you just created yourself; return new arrays from functions that receive arrays from somewhere else.

  6. 6

    Two traps: delete arr[1] removes the value but leaves a hole and keeps length the same; use splice(1, 1) instead. And to empty an array that others share, arr.length = 0 clears it in place, while arr = [] only points your own variable at a new empty array (and fails if arr is const).

Explain it without notes

01

What do push, pop, shift and unshift return?

02

Explain the arguments and return value of splice.

03

What is the difference between a mutating and a non-mutating array method, and why does it matter?

04

Why is shift slower than pop on a large array?

Practice

01

Start with const tasks = ["wash", "cook"];. Add "shop" at the start and "sleep" at the end, then remove "cook" with splice. Print the array.

02

Write a function removeAt(arr, i) that returns a new array without the item at index i and doesn't change arr.

Trade-offs

  • ↔

    Mutating in place uses no extra memory and is fastest; returning copies is safer when an array is shared, and is what React and other state libraries expect, at the cost of allocating a new array each time.

  • ↔

    unshift/shift are convenient but O(n); for big queues a head index or a dedicated queue structure is faster.

Done when you can

  • I can add and remove items at the start and end and know what each method returns.

  • I can remove, insert and replace items in the middle with splice.

  • I can tell slice from splice.

  • I can name which array methods mutate and which return a new array.

  • I can use with and toSpliced to change a copy instead of the original.