pop()
ES3+Removes the last element from an array and returns that element.
Syntax
array.pop()Return Value
The removed element, or undefined if empty
Examples
const fruits = ['apple', 'banana', 'cherry'];
const last = fruits.pop();
console.log(last);
console.log(fruits); 📌 When to Use
Use pop() when removing the last element and using its value are the same operation: taking the top item off a stack, consuming a work queue in LIFO order, undoing the most recent action, or trimming the newest entry from a capped history. The pairing with push() is what makes it valuable - together they give you a textbook stack with O(1) operations at the shared end of the array, which underlies undo systems, depth-first traversals with an explicit stack, backtracking algorithms, and matching-bracket parsers. Two clarifying contrasts define when NOT to use it. If you merely need to look at the last element, arr.at(-1) (ES2022) or arr[arr.length - 1] reads without destroying - popping just to peek, then pushing back, is a code smell. If the array is immutable by convention (framework state), derive a new array with arr.slice(0, -1) and read the last element separately; pop() on state mutates behind the framework's back. Finally, mind which end you consume from: pop() takes the newest element (LIFO). For first-in-first-out processing take from the front - conceptually shift(), though for large queues an index pointer or a real queue structure avoids the O(n) shifting cost.
⚠️ Common Mistakes
Underestimating that pop() is a mutation: calling it "just to get the last value" inside a getter, a render function, or a computed property shrinks the array by one on every invocation. Because the return value looks correct each time, the disappearing elements are typically discovered far away from the innocent-looking accessor that eats them.
Skipping the empty check. pop() on an empty array returns undefined without throwing, so a consumer loop that trusts the value crashes one step later on property access - or worse, propagates undefined into stored data. Guard with arr.length > 0, or treat undefined explicitly when elements can never legitimately be undefined.
Popping to peek: reading the top of a stack by popping and re-pushing performs two mutations for zero net change and briefly leaves the structure inconsistent - a hazard if anything observes it in between. arr.at(-1) answers "what is on top?" without touching anything.
Using while (arr.pop()) to drain a stack. The loop terminates early when a legitimately falsy element - 0, "", false, null - reaches the top, leaving the rest unprocessed. Test the length instead: while (arr.length) { const item = arr.pop(); ... }.
Popping from framework state in place. const last = items.pop() followed by setItems(items) keeps the same array reference, so change detection sees nothing new. Read the last element first, then set state to items.slice(0, -1) - both pieces derived without mutating the original.
✅ Best Practices
Split the two effects of pop() when immutability matters: const last = arr.at(-1) captures the value and const rest = arr.slice(0, -1) builds the shortened array. The pair costs one allocation and leaves every existing reference to arr valid - exactly what state management systems require.
Prefer arr.at(-1) over arr[arr.length - 1] for non-destructive tail reads: it says "last element" directly, needs no length arithmetic, and returns undefined on empty arrays instead of tempting an off-by-one. Reserve pop() strictly for take-and-remove semantics.
Keep stack discipline explicit: elements enter with push() and leave with pop(), nothing touches the middle. Encapsulating the array in a small class or closure with only push/pop/peek exposed prevents accidental splices that would silently break LIFO ordering guarantees.
Combine the guard and the take in one pattern for consumer loops: while (stack.length) { const job = stack.pop(); ... } processes newest-first and terminates cleanly, with no falsy-value pitfalls and no possibility of reading past the start.
Cap history structures at removal time: after pushing a new undo entry, if (undoStack.length > LIMIT) undoStack.shift() drops the oldest. The push/pop pair handles the hot path in O(1); the rare overflow trim is the only O(n) moment, which is the right trade for a bounded buffer.
⚡ Performance Notes
pop() is true O(1): it reads the last slot, decrements length, and returns the value - no other element moves, no allocation happens. In V8 the backing store simply keeps its capacity (shrinking is deferred), so a pop immediately followed by a push reuses the same memory with zero resizing cost, which is why push/pop stacks are essentially free even under heavy churn. This is the decisive contrast with shift(): consuming 100,000 items via pop() performs 100,000 constant-time operations, while shift() would move every remaining element on every call, totaling billions of element copies. Structure algorithms to consume from the tail whenever order permits - depth-first search, work-stealing queues, and object pools all exploit this. When FIFO order is mandatory, do not fake it by reversing repeatedly; reverse once and then pop, or track a head index. The immutable alternative slice(0, -1) is O(n) per operation, a cost worth paying only at state-management boundaries, not inside tight loops.
🌍 Real World Example
Implementing Browser History Navigation
Back navigation is inherently LIFO - the page you return to is the most recently visited one - so a push/pop stack models it exactly: navigate() pushes the new URL, goBack() pops the current one and renders whatever is now on top. Notice the two subtleties the implementation handles: the guard requires length > 1 rather than length > 0, because the stack must never pop its final entry (there would be nowhere left to render), and after popping, the code reads the new top with an index access instead of another pop - a peek, not a consume. The same skeleton drives undo/redo stacks, modal navigation, and wizard steps.
const history = [];
function navigate(url) {
history.push(url);
renderPage(url);
}
function goBack() {
if (history.length > 1) {
history.pop(); // Remove current page
const previousUrl = history[history.length - 1];
renderPage(previousUrl);
return true;
}
return false; // Can't go back further
}
navigate('/home');
navigate('/products');
navigate('/cart');
goBack(); // Returns to /products