Table of Contents

What Is the JavaScript Array push() Method?

push() is a built-in, prototype-based method on Array.prototype that appends one or more elements to the end of an array, mutating the original array in place, and returning the array’s new length. It is the counterpart to pop(), which removes the last element. Together, push() and pop() implement the LIFO (last-in, first-out) stack behavior that underpins countless algorithms, parsers, and state machines in JavaScript.

Syntax and Formal Specification

The method adheres to a variadic signature: push(element1, element2, ..., elementN). It accepts zero or more arguments, appends them sequentially to the calling array, and returns the new length property of the object upon which it was invoked.

javascript
const fruits = ["apple", "banana"];
const newLength = fruits.push("cherry", "date");
console.log(fruits);      // ["apple", "banana", "cherry", "date"]
console.log(newLength);   // 4

Parameters

  • element1, ..., elementN — Zero or more values of any type (primitives, objects, functions, null, undefined, nested arrays). Each argument is appended as its own element in the exact order provided.

  • No fixed arity — push() accepts any number of arguments, including none.

javascript
const arr = [1, 2, 3];
console.log(arr.push());   // 3 (no arguments, no change)
console.log(arr);          // [1, 2, 3]

Return Value

An integer equal to the new length of the array after the append. Because it returns a number, push() is not chainable with other array methods.

javascript
const stack = [];
stack.push("a");            // 1
stack.push("b", "c");       // 3
console.log(stack);         // ["a", "b", "c"]

The Generic Algorithm

push() is intentionally generic. The ECMAScript specification does not restrict it to Array instances. Instead, the algorithm operates on any object possessing a length property and integer-indexed properties. When invoked, the method:

  1. Reads this.length and converts it to an integer (len).

  2. Iterates through the provided arguments, assigning each to this[len], this[len+1], and so forth.

  3. Sets this.length to the final value of len after all arguments have been assigned.

This genericity allows push() to operate on array-like objects via Function.prototype.call or Reflect.apply.

javascript
const collection = {
  length: 0,
  add(item) {
    Array.prototype.push.call(this, item);
  }
};
collection.add("alpha");
collection.add("beta");
console.log(collection.length);   // 2
console.log(collection[0]);       // "alpha"
console.log(collection[1]);       // "beta"

Strings are explicitly unsuitable for this technique because they are immutable; assignment to indexed properties fails silently in sloppy mode or throws a TypeError in strict mode.

javascript
"use strict";
Array.prototype.push.call("abc", "d");
// TypeError: Cannot assign to read only property '3' of string 'abc'

Mutability and the Immutability Trade-off

push() is a mutating method. It alters both the content and the length of the original array in place. This is the correct choice when the array is a local accumulator, a buffer, or a data structure whose identity must be preserved (e.g., a reactive store or a class-owned collection).

javascript
const buffer = [];
function log(message) {
  buffer.push(`[${new Date().toISOString()}] ${message}`);
}
log("Server started");
log("Request received");
console.log(buffer.length);   // 2

When immutability is required—such as in Redux reducers, React state updates, or functional pipelines—push() is the wrong tool. The idiomatic alternatives are:

Approach Mechanism Use Case
arr.concat([item]) Returns a new array; original untouched When item is already known
[...arr, item] Spread syntax creates a shallow copy Modern, readable, preferred for single or few items
arr.concat(items) Concatenates arrays without spreading elements When items is already an array
javascript
// Mutating (fine for local accumulators)
const local = [1, 2];
local.push(3);                    // local: [1, 2, 3]

// Immutable (required for state management)
const state = [1, 2];
const nextState = [...state, 3];  // state: [1, 2], nextState: [1, 2, 3]

A critical distinction: concat flattens array arguments one level. If you call arr.push([1, 2]), the entire array [1, 2] becomes a single element at the end. Calling arr.concat([1, 2]) spreads 1 and 2 as separate elements.

javascript
const a = [0];
a.push([1, 2]);
console.log(a);                    // [0, [1, 2]]

const b = [0];
console.log(b.concat([1, 2]));     // [0, 1, 2]

Performance: Amortized Constant Time and the Reallocation Boundary

The theoretical time complexity of push() is O(1) per operation. In practice, it is amortized O(1). JavaScript engines implement arrays as dynamic slot arrays. When the underlying storage is full, the engine must allocate a larger block of memory and copy existing elements into it. This reallocation incurs an O(n) cost, but because the new capacity is typically grown by a factor (commonly around 1.5× to 2×), the expensive copy operation occurs at exponentially decreasing frequencies. Over a sequence of n pushes, the total cost remains O(n), yielding an amortized O(1) per push.

This behavior contrasts sharply with unshift(), which prepends elements. Because the start of the array is occupied, unshift() typically requires reallocating memory and copying all existing elements on every operation, resulting in linear time per call and quadratic behavior over a series of unshifts.

javascript
// Fast: O(1) amortized
const append = [];
for (let i = 0; i < 100000; i++) append.push(i);

// Slow: O(n) per call, O(n^2) total
const prepend = [];
for (let i = 0; i < 100000; i++) prepend.unshift(i);

The Sparse Array Caveat

For sparse arrays (arrays with holes, created via new Array(n) without filling), engines may use dictionary-mode representations. In such cases, push() performance characteristics differ, and some engines exhibit constant time for pushes into sparse arrays implemented as hash tables. However, this is an implementation detail, not a guarantee. Dense arrays initialized with literal syntax ([]) generally provide the most predictable and optimized push performance.

javascript
const dense = [];             // Fast-path internal representation
const sparse = new Array(1000); // May trigger dictionary mode

Preallocation vs. push()

For performance-critical accumulation with a known final size, preallocating and assigning by index can outperform push() because it avoids capacity checks and reallocation logic.

javascript
// Known size: preallocate and assign
const n = 1000000;
const preallocated = new Array(n);
for (let i = 0; i < n; i++) preallocated[i] = i;

// Unknown size: push is natural and efficient
const dynamic = [];
for (let i = 0; i < n; i++) dynamic.push(i);

Benchmarking in the target runtime is the only reliable way to determine the better approach for a specific workload.

push() with Spread Syntax: Merging and Argument Limits

The spread operator (...) combines naturally with push() to merge arrays:

javascript
const target = [1, 2, 3];
const source = [4, 5, 6];
target.push(...source);
console.log(target);   // [1, 2, 3, 4, 5, 6]

This pattern appends each element of source individually. It is essential to recognize the argument count limit. JavaScript engines impose a maximum number of arguments that can be passed to a function. Spreading a very large array (typically in the range of tens of thousands to hundreds of thousands of elements, depending on the engine) into push() will throw a RangeError.

javascript
const huge = new Array(200000).fill(0);
const target = [];
// target.push(...huge);   // RangeError: Maximum call stack size exceeded

For merging large arrays, concat() or an iterative push loop is safer:

javascript
function appendAll(target, source) {
  for (let i = 0; i < source.length; i++) {
    target.push(source[i]);
  }
  return target;
}

const merged = appendAll([], huge);
console.log(merged.length);   // 200000