JS Array Push and Pop Methods: A Complete Guide
Master the JavaScript push() and pop() methods for adding and removing elements from the end of arrays. Covers syntax, return values, real-world patterns, performance characteristics, stack implementation, and common pitfalls.
push() and pop() are the two most-used array methods in JavaScript. They add and remove elements from the end of an array, making them the foundation of stack-based patterns, queue processing, and everyday list manipulation. Despite their simplicity, developers regularly misuse their return values, accidentally mutate shared state, and miss performance implications when working with large datasets.
This guide covers both methods in depth with real-world examples and patterns you will actually use in production code.
The push() Method
push() adds one or more elements to the end of an array and returns the new length (not the array itself):
const cart = ["Laptop", "Mouse"];
const newLength = cart.push("Keyboard");
console.log(cart); // ["Laptop", "Mouse", "Keyboard"]
console.log(newLength); // 3Syntax
array.push(element1)
array.push(element1, element2, ..., elementN)Pushing Multiple Elements
You can add several elements in a single call:
const colors = ["red"];
colors.push("green", "blue", "yellow");
console.log(colors); // ["red", "green", "blue", "yellow"]Pushing from Another Array
To append all elements from one array onto another, use the spread operator:
const existing = [1, 2, 3];
const additional = [4, 5, 6];
existing.push(...additional);
console.log(existing); // [1, 2, 3, 4, 5, 6]Spread Limit
Using push(...largeArray) with extremely large arrays (100,000+ elements) can hit the JavaScript engine's maximum call stack size. For very large merges, use a loop or Array.prototype.concat() instead.
Return Value Trap
The most common push() mistake is assuming it returns the modified array:
// Bug: push() returns the new length, not the array
const result = [1, 2, 3].push(4);
console.log(result); // 4 (the length, not the array!)
// What the developer expected:
// [1, 2, 3, 4]
// Fix: if you need the array back, use spread
const arr = [1, 2, 3];
const withFour = [...arr, 4]; // [1, 2, 3, 4]The pop() Method
pop() removes the last element from an array and returns that element:
const stack = ["A", "B", "C", "D"];
const removed = stack.pop();
console.log(removed); // "D"
console.log(stack); // ["A", "B", "C"]Syntax
const removedElement = array.pop()Popping from an Empty Array
Calling pop() on an empty array returns undefined without error:
const empty = [];
const result = empty.pop();
console.log(result); // undefined
console.log(empty.length); // 0push() and pop() at a Glance
| Property | push() | pop() |
|---|---|---|
| Action | Adds element(s) to end | Removes last element |
| Parameters | One or more values | None |
| Returns | New array length | Removed element |
| Mutates original | Yes | Yes |
| Time complexity | O(1) amortized | O(1) |
| On empty array | Adds normally | Returns undefined |
Building a Stack with push() and pop()
A stack follows Last-In-First-Out (LIFO) order, where the most recent addition is the first to be removed. push() and pop() naturally implement this pattern:
class BrowserHistory {
constructor() {
this.pages = [];
}
visit(url) {
this.pages.push(url);
console.log(`Navigated to: ${url}`);
}
back() {
if (this.pages.length === 0) {
console.log("No history to go back to");
return null;
}
const previous = this.pages.pop();
console.log(`Going back from: ${previous}`);
return previous;
}
current() {
return this.pages.at(-1) || "No page loaded";
}
}
const history = new BrowserHistory();
history.visit("https://example.com");
history.visit("https://example.com/about");
history.visit("https://example.com/contact");
console.log(history.current()); // "https://example.com/contact"
history.back(); // "Going back from: https://example.com/contact"
console.log(history.current()); // "https://example.com/about"Real-World Patterns
Undo/Redo System
class UndoManager {
constructor() {
this.history = [];
this.redoStack = [];
}
execute(action) {
action.do();
this.history.push(action);
this.redoStack.length = 0; // Clear redo after new action
}
undo() {
const action = this.history.pop();
if (!action) return;
action.undo();
this.redoStack.push(action);
}
redo() {
const action = this.redoStack.pop();
if (!action) return;
action.do();
this.history.push(action);
}
}
// Usage with a text editor
const manager = new UndoManager();
let text = "";
manager.execute({
do() { text += "Hello"; },
undo() { text = text.slice(0, -5); },
});
console.log(text); // "Hello"
manager.execute({
do() { text += " World"; },
undo() { text = text.slice(0, -6); },
});
console.log(text); // "Hello World"
manager.undo();
console.log(text); // "Hello"
manager.redo();
console.log(text); // "Hello World"Collecting Form Entries
const formEntries = [];
function addEntry(fieldName, value) {
if (!fieldName || value === undefined) {
throw new Error("Field name and value are required");
}
formEntries.push({
field: fieldName,
value: value,
timestamp: Date.now(),
});
}
function removeLastEntry() {
const removed = formEntries.pop();
if (removed) {
console.log(`Removed entry: ${removed.field} = ${removed.value}`);
}
return removed;
}
addEntry("name", "Alice Johnson");
addEntry("email", "alice@example.com");
addEntry("role", "Developer");
console.log(formEntries.length); // 3
removeLastEntry(); // "Removed entry: role = Developer"
console.log(formEntries.length); // 2Batch Processing Queue
Processing items in batches using pop() to drain a work list:
async function processInBatches(items, batchSize, processFn) {
const queue = [...items]; // Clone to avoid mutating input
const results = [];
while (queue.length > 0) {
const batch = [];
for (let i = 0; i < batchSize && queue.length > 0; i++) {
batch.push(queue.pop());
}
const batchResults = await Promise.all(batch.map(processFn));
results.push(...batchResults);
console.log(`Processed batch. Remaining: ${queue.length}`);
}
return results;
}
// Example usage
const urls = Array.from({ length: 20 }, (_, i) => `https://api.example.com/item/${i}`);
// processInBatches(urls, 5, fetchData);push() and pop() vs Alternatives
When should you choose mutation over immutable patterns?
Use push()/pop() when:
- You own the array and no other code references it
- Performance matters (mutation is faster than creating new arrays)
- You are building an internal data structure (stack, queue, buffer)
Use spread/slice when:
- The array is shared state (React state, Redux store, function arguments)
- You need the original array preserved
- You are chaining transformations
Performance Deep Dive
Both push() and pop() operate in O(1) amortized time. This means individual calls complete in constant time, with occasional pauses when the engine needs to resize the underlying storage.
// Benchmark: push 1 million elements
const arr = [];
console.time("push-million");
for (let i = 0; i < 1_000_000; i++) {
arr.push(i);
}
console.timeEnd("push-million"); // ~15-25ms in V8
// Benchmark: pop all 1 million elements
console.time("pop-million");
while (arr.length > 0) {
arr.pop();
}
console.timeEnd("pop-million"); // ~8-15ms in V8Compare this to unshift() which must re-index every element:
| Operation | 10 items | 10,000 items | 1,000,000 items |
|---|---|---|---|
push() | ~0.001ms | ~0.5ms | ~20ms |
pop() | ~0.001ms | ~0.3ms | ~12ms |
unshift() | ~0.001ms | ~2ms | ~500ms+ |
shift() | ~0.001ms | ~1.5ms | ~400ms+ |
Common Mistakes
Chaining on push() (returns length, not array):
// Bug: push returns the new length
const result = [].push(1).push(2); // TypeError: (3).push is not a function
// Fix: chain properly
const arr = [];
arr.push(1);
arr.push(2);
// Or use a builder pattern: [...[], 1, 2]Mutating state in React:
// Bug: React won't re-render because the reference hasn't changed
function addTodo(todo) {
todos.push(todo); // Mutates existing array
setTodos(todos); // Same reference — React skips re-render
}
// Fix: create a new array
function addTodo(todo) {
setTodos([...todos, todo]); // New array — React detects change
}Forgetting pop() returns the removed element:
const stack = [10, 20, 30];
// Developer forgot to capture the return value
stack.pop();
// The value 30 is gone forever
// Correct: capture it if you need it
const last = stack.pop();
console.log(last); // 20Rune AI
Key Insights
- push() adds to end, pop() removes from end: Both are O(1) and mutate the original array
- Return values matter:
push()returns the new length;pop()returns the removed element - Stack pattern:
push()andpop()naturally implement LIFO (Last-In-First-Out) data structures - Mutation awareness: Never use
push()/pop()on shared state or React state; use spread orslice()instead - Performance advantage: End operations are dramatically faster than start operations (
shift()/unshift()) on large arrays
Frequently Asked Questions
Does push() return the new array?
What is the difference between push() and concat()?
Can push() add multiple elements at once?
Is pop() faster than shift()?
How do I pop multiple elements at once?
Conclusion
push() and pop() are the workhorses of array manipulation in JavaScript. They operate at the end of the array in O(1) time, making them ideal for stacks, buffers, history tracking, and any pattern where you process data in LIFO order. The critical details to remember are that push() returns the new length (not the array), both methods mutate the original, and mutation-based patterns should be replaced with spread or slice when working with shared state in frameworks like React.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.