push, pop and splice are the three methods that make an array feel alive, and they are also the three that surprise people most. Here is what each of them returns, what they change, and how to remove an item when the only thing you know about it is its value.
What do push and pop actually do?
push appends items to the end and gives back the new length. pop removes the last item and gives back that item. Both mutate the array in place, so the array you passed in is the array you get back — no copy is made anywhere.
const cart = ['notebook'];
cart.push('pen', 'eraser');
console.log(cart.length); // 3
console.log(cart); // [ 'notebook', 'pen', 'eraser' ]
const removed = cart.pop();
console.log(removed); // eraser
console.log(cart); // [ 'notebook', 'pen' ]
Notice that push takes any number of items, so one call added two entries. pop ignores any arguments and always removes exactly one item from the end. On an empty array pop returns undefined instead of throwing, which means a loop written without a length check will quietly push undefined values into your data rather than failing loudly.
How do shift and unshift differ from push and pop?
They are the front-end twins. unshift puts items at the front and returns the new length; shift removes the first item and returns it.
const queue = ['b', 'c'];
queue.unshift('a');
console.log(queue); // [ 'a', 'b', 'c' ]
console.log(queue.shift()); // a
console.log(queue); // [ 'b', 'c' ]
Look at the order of the last two lines: shift hands back the element that was first, not the element that is first afterwards. Remember that when you are chaining calls, because queue.shift().name reads the name of the item you just removed.
What does splice return, and what does it change?
splice is the general-purpose editor. It takes a start index, a delete count, and any number of items to insert, and it does all three in a single call. Its return value is the array of items it removed — that detail catches almost everyone out the first time.
const tasks = ['write', 'test', 'review', 'deploy'];
const cut = tasks.splice(1, 2);
console.log(cut); // [ 'test', 'review' ]
console.log(tasks); // [ 'write', 'deploy' ]
tasks.splice(1, 0, 'lint', 'build');
console.log(tasks); // [ 'write', 'lint', 'build', 'deploy' ]
tasks.splice(-1, 1);
console.log(tasks); // [ 'write', 'lint', 'build' ]
Read the first pair of lines together. By the time the splice call returns, the original array has already lost two items; the value that comes back is the removed part. If you were expecting splice to hand you what is left, the two are backwards. A negative start index counts from the end, so splice(-1, 1) removes the final item, and a delete count of zero turns splice into a pure insert.
How do you remove an item when you only know its value?
There is no removeByValue method, so you do it in two steps: find the index with indexOf, then delete that index with splice. The guard matters. indexOf returns -1 when nothing matches, and splice(-1, 1) would quietly delete the last element instead of doing nothing at all.
const labels = ['js', 'web', 'node'];
const at = labels.indexOf('web');
if (at !== -1) labels.splice(at, 1);
console.log(labels); // [ 'js', 'node' ]
const missing = labels.indexOf('rust');
console.log(missing); // -1
indexOf compares with strict equality, so it will not find the number 3 in an array that holds the string '3'. When the value came from a text input, normalise it first. If you want the original array left untouched, filter is the cleaner tool: labels.filter((label) => label !== 'web') gives you a new array with the item already gone.
Why are shift and unshift slower than push and pop?
An array is a contiguous run of values in memory. Appending at the end is cheap because the engine either has spare capacity or copies once. Inserting at the front means every remaining element has to move along by one position so the indices stay contiguous. The practical consequence: use push and pop for queues, and keep shift and unshift for lists that stay small. The same reasoning makes splice near the end cheaper than splice in the middle. Removing one specific item from a list of twenty is fine either way; rebuilding a list of fifty thousand on every tick is not.
How do you clear an array without breaking other references?
This comes up more often than you would expect. If other code holds a reference to your array, replacing your variable with a brand-new empty array leaves that code pointing at the old, still-full array. Truncating with length = 0 empties the same object in place, so every reference sees it empty.
const log = ['x', 'y'];
log.length = 0;
console.log(log, log.length); // [] 0
const history = ['a', 'b', 'c'];
history.length = 1;
console.log(history); // [ 'a' ]
The second half shows the same trick used as a trim: assigning a smaller length deletes the tail without naming each item.
Should you mutate the array or copy it?
Every method on this page mutates. That is fast and perfectly reasonable for state you own. It turns into a bug when the array came from somewhere else — a function argument, a fetched response, a slice of shared state — because the caller can see your edits and your function has quietly become part of their data flow. In that case build a new array instead of editing the one you were handed.
| Method | Returns | Mutates? | Typical use |
|---|---|---|---|
push(...items) | New length | Yes | Append to the end |
pop() | The removed item | Yes | Take the last item |
shift() | The removed item | Yes | Take the first item |
unshift(...items) | New length | Yes | Add to the front |
splice(start, count, ...items) | The removed items | Yes | Insert or delete anywhere |
The working rule is short: mutate the array you created, copy the array you were given. The next lesson looks at the loop styles that make copying and transforming painless, and the one after that covers the search-and-aggregate methods.
Try it yourself: open a file called queue.js and start with const tasks = ['write', 'test', 'deploy'];. Print the array after each step as you go. Push 'ship' on the end, then unshift 'plan' onto the front, then splice out the item at index 1. Now remove 'deploy' by value using indexOf plus splice and print the final array. Finish by setting tasks.length = 0 and printing the length again — the array should be empty without you deleting anything by name.