push()

ES3+

Adds one or more elements to the end of an array and returns the new length.

Syntax

array.push(element1, element2, ...)

Parameters

elements any

Elements to add to the end of the array

Return Value

number

The new length of the array

Examples

JavaScript
const fruits = ['apple', 'banana'];
const newLength = fruits.push('cherry');
console.log(fruits);
console.log(newLength);
Output:
// ['apple', 'banana', 'cherry'] 3

📌 When to Use

Use push() to append to the end of an array you own and are actively building: collecting validation errors as checks run, accumulating results inside a loop that map() cannot express (multiple outputs per input, early termination, interleaved async work), buffering log lines or events before a flush, or implementing a stack together with pop(). Appending at the end is the cheapest possible array mutation, which is why "push into a local array, return it" is the standard shape for constructing lists imperatively. The decision points are about context, not capability. If the loop produces exactly one value per element, map() replaces the push loop with a single declarative expression. If the array is framework state or shared with other code, produce a new array instead - [...state, item] - because push() keeps the same reference and change detection will not fire. If elements must enter at the front, unshift() exists but costs O(n) per call; pushing and reversing once at the end, or reading the array backwards, is usually smarter. And when merging whole arrays, arr.push(...items) spreads the elements in place efficiently, while concat() returns a merged copy - choose by whether mutation of the target is desired.

⚠️ Common Mistakes

Pushing an array when you meant to push its elements: arr.push(items) and arr.push([...items]) both append ONE element that happens to be an array, producing [1, 2, [3, 4]]. The nested array then breaks length checks and flattening assumptions downstream. Spreading into the call - arr.push(...items) - appends each element individually.

Chaining off the return value: const result = arr.push(x).map(...) throws immediately, because push() returns the new LENGTH (a number), not the array. The design predates fluent APIs. Push first, then use the array variable on the following line - or use concat()/spread when an expression-style append is genuinely needed.

Pushing into framework state: todos.push(newTodo) mutates the array behind the same reference, so React sees "no change" and skips the re-render - the todo appears only after some unrelated update forces a repaint, making the bug look intermittent. Create the new state instead: setTodos([...todos, newTodo]).

Spreading a huge array into push(): arr.push(...millionItems) passes every element as a separate function argument and can throw RangeError (call-stack or argument-count limits, typically somewhere past 100,000 elements depending on engine). For bulk merges of unbounded size, loop with chunks or use concat().

Pushing to the array you are currently iterating with forEach() or map(). The iteration range is fixed at the original length, so the appended elements are silently never visited - or with a manual loop condition re-reading length, you get an infinite loop. Collect additions in a second array and merge after the pass.

✅ Best Practices

Draw the mutability line consciously: inside a function building a local array, push() is ideal - fast, clear, and invisible to callers; the moment the array crosses a boundary (state, props, module exports, function parameters), switch to [...arr, newItem] so consumers can rely on reference equality to detect changes.

Batch appends into one call when the items are known together: arr.push(a, b, c) performs a single length update and grows capacity once, and it also reads as one logical action. The same applies to arr.push(...smallArray) for merging a bounded chunk into an accumulator.

Use arr.push(...otherArr) to merge another array in place - but only for arrays of bounded, moderate size, since each spread element becomes a function argument. For unbounded data (file contents, API pages), append with a loop or build the merged array once with concat().

Model a stack with push() and pop() and give the array a name that says so (undoStack, openTags): the pair is O(1) at the tail end, and naming the discipline prevents teammates from "helpfully" shifting from the front and destroying the LIFO invariant.

Ignore the returned length unless you immediately need it; assigning it to a variable named anything like "arr" or "result" invites the next developer to treat it as the array. When the length matters, arr.length on the next line is equally fast and unambiguous.

⚡ Performance Notes

push() is amortized O(1): V8 backs arrays with a contiguous elements store that grows geometrically (roughly capacity * 1.5 + 16) when full, so most pushes are a single write plus a length update, with an occasional O(n) copy into a larger store whose cost amortizes to constant per element. This makes push-in-a-loop the fastest way to build an array incrementally - dramatically faster than looped concat() or [...acc, x], both of which copy everything so far on every iteration (O(n^2) total). Two V8-specific notes: pushing a value that widens the element kind (the first float into an integer array, the first object into a float array) triggers a one-time transition of the whole store, so keeping arrays type-homogeneous preserves the fastest paths; and writing past the end via arr[arr.length] = x is optimized identically to push() in modern V8, so choose push() for readability. If the final size is known, new Array(n) plus indexed writes can avoid regrowth, but mind that it starts holey - filling every slot immediately restores packed status.

🌍 Real World Example

Collecting Form Validation Errors

Validation is a naturally imperative accumulation: each rule either passes silently or contributes an error message, rules may depend on earlier results (only check format if the field is present at all - note the else-if structure), and the count of outputs is unknowable up front. That shape fits push() better than any declarative method. The function builds a local array invisible to the outside world until it is returned, so mutation here is completely safe and idiomatic - the immutability concerns around push() apply to shared state, not to local accumulators. Callers get a simple contract: an empty array means the form is valid, and a non-empty one is ready to render as a message list.

function validateForm(data) {
  const errors = [];

  if (!data.email) {
    errors.push('Email is required');
  } else if (!data.email.includes('@')) {
    errors.push('Invalid email format');
  }

  if (!data.password) {
    errors.push('Password is required');
  } else if (data.password.length < 8) {
    errors.push('Password must be at least 8 characters');
  }

  return errors;  // ['Invalid email format', 'Password must be at least 8 characters']
}

Related Methods