How to Add Elements to the Beginning of a JavaScript Array: Complete Guide (2026)
Get a summary of this article:
Last Updated: May 7, 2026
Key Takeaways
- unshift() is the simplest method for adding elements to the start of an array, but it mutates the original array and has O(n) time complexity
- Spread operator [newItem, …array] is the modern standard for immutable prepending, required in React and functional programming
- Performance matters at scale: prepending to an array with 100,000 elements requires shifting all 100,000 existing elements
- Choose mutable vs immutable based on your context. React state requires immutability; utility scripts can use mutation
- Enterprise datasets benefit from alternative data structures when frequent prepending is a core operation

How Array Prepending Works Internally
When you add an element to the beginning of an array, the JavaScript engine performs several operations under the hood. Understanding this explains why some methods are faster than others.
Step 1: The engine checks if the current memory allocation can hold one more element. If not, it allocates a new, larger block of memory.
Step 2: Every existing element must shift one position to the right. Element at index 0 moves to index 1, index 1 moves to index 2, and so on through the entire array.
Step 3: The new element is inserted at index 0.
This shifting operation is why prepending has O(n) time complexity; the time it takes grows linearly with the number of elements. For an array with 10 elements, that’s trivial. For an array with 1,000,000 elements, that’s 1,000,000 shift operations.
What happens internally when you prepend to [1, 2, 3]:
Before: [1, 2, 3]
- Step 1: [_, 1, 2, 3] — shift all elements right
- Step 2: [0, 1, 2, 3] — insert new element at index 0
The engine moves EVERY element — this is why it’s O(n)
Compare this to push(), which adds to the end — no shifting needed, just place the new element at the next available position. That’s O(1) — constant time regardless of array size.
Method 1: Array.unshift()
unshift() is the most straightforward way to add elements to the beginning of an array. It mutates the original array and returns the new length.
Add a single element.
const fruits = ['banana', 'cherry', 'date'];
const newLength = fruits.unshift('apple');
console.log(fruits); // ['apple', 'banana', 'cherry', 'date']
console.log(newLength); // 4
Adding multiple elements at once:
const fruits = ['cherry', 'date'];
fruits.unshift('apple', 'banana');
console.log(fruits); // ['apple', 'banana', 'cherry', 'date']
Elements are inserted in the order they are passed.
Adding elements from another array using spread:
const existing = ['cherry', 'date'];
const newFruits = ['apple', 'banana'];
existing.unshift(...newFruits);
console.log(existing); // ['apple', 'banana', 'cherry', 'date']
When to use unshift()
- Simple scripts where mutation is acceptable
- When you need to add to the beginning and don’t care about immutability
- When working with arrays under 10,000 elements (performance is negligible)
- When you need the new array length as a return value
When NOT to use unshift()
- React state updates (requires immutability)
- Functional programming patterns
- Performance-critical code with arrays over 100,000 elements
- When you need to preserve the original array
Method 2: Spread Operator
The spread operator … is the modern JavaScript standard for immutable array prepending. It creates a new array, leaving the original untouched.
const original = ['banana', 'cherry', 'date'];
const updated = ['apple', ...original];
console.log(updated); // ['apple', 'banana', 'cherry', 'date']
console.log(original); // ['banana', 'cherry', 'date'] — unchanged
Adding multiple elements:
const original = ['cherry', 'date'];
const updated = ['apple', 'banana', ...original];
console.log(updated); // ['apple', 'banana', 'cherry', 'date']
console.log(original); // ['cherry', 'date'] — unchanged
Merging two arrays with a new element at the start:
const listA = ['banana', 'cherry'];
const listB = ['date', 'elderberry'];
const combined = ['apple', ...listA, ...listB];
console.log(combined); // ['apple', 'banana', 'cherry', 'date', 'elderberry']
Why the spread operator is preferred in modern JavaScript
The spread syntax clearly communicates intent. When a developer reads [’newItem’, …existingArray], they immediately understand that a new array is being created with the new item at the front. This readability advantage matters in team environments where code is reviewed and maintained by multiple people.
The spread operator also works naturally with React’s state management, where immutability is not optional; it’s required for the virtual DOM to detect changes.
Method 3: Array.concat()
concat() creates a new array by joining arrays or values together. It’s the classic immutable approach, predating the spread operator.
const original = ['banana', 'cherry', 'date'];
const updated = ['apple'].concat(original);
console.log(updated); // ['apple', 'banana', 'cherry', 'date']
console.log(original); // ['banana', 'cherry', 'date'] — unchanged
Adding multiple elements:
const original = ['cherry', 'date'];
const updated = ['apple', 'banana'].concat(original);
console.log(updated); // ['apple', 'banana', 'cherry', 'date']
concat() vs spread — what’s the difference?
Functionally, they are identical for prepending. The difference is syntax preference:
// These produce the same result:
const resultA = ['apple', ...original]; // Spread — modern, concise
const resultB = ['apple'].concat(original); // Concat — classic, explicit
// Both create new arrays. Both leave the original unchanged.
concat() has a slight edge in one specific scenario — when you are concatenating variables that might be null or undefined. The spread operator throws an error on non-iterables, while concat() handles them as individual values.
Method 4: Array.splice()
splice() can insert elements at any position, including the beginning. It mutates the original array.
const fruits = ['banana', 'cherry', 'date'];
fruits.splice(0, 0, 'apple');
// splice(startIndex, deleteCount, ...itemsToInsert)
console.log(fruits); // ['apple', 'banana', 'cherry', 'date']
Adding multiple elements at the beginning:
const fruits = ['cherry', 'date'];
fruits.splice(0, 0, 'apple', 'banana');
console.log(fruits); // ['apple', 'banana', 'cherry', 'date']
splice() is versatile but verbose for simple prepending. Its real strength is inserting at any arbitrary position:
const fruits = ['apple', 'date', 'elderberry'];
fruits.splice(1, 0, 'banana', 'cherry');
// Insert 'banana' and 'cherry' at index 1
console.log(fruits); // ['apple', 'banana', 'cherry', 'date', 'elderberry']
Use splice() when you need to insert at a specific index. For simply adding to the beginning, unshift(), or the spread operator, is clearer.
Method 5: Array.prototype.with() (ES2023+)
The with() method, introduced in ES2023, creates a new array with one element replaced at a given index. While it does not directly prepend, combined with spread, it provides a fully immutable approach:
// with() replaces an element at an index — returns a new array
const original = ['banana', 'cherry', 'date'];
// To prepend, combine with spread:
const updated = ['apple', ...original];
// with() is more useful for replacing at a specific index:
const replaced = original.with(1, 'coconut');
console.log(replaced); // ['banana', 'coconut', 'date']
console.log(original); // ['banana', 'cherry', 'date'] — unchanged
with() is part of the new immutable array methods in ES2023 alongside toSorted(), toReversed(), and toSpliced(). The toSpliced() method is particularly relevant for prepending:
const original = ['banana', 'cherry', 'date'];
const updated = original.toSpliced(0, 0, 'apple');
console.log(updated); // ['apple', 'banana', 'cherry', 'date']
console.log(original); // ['banana', 'cherry', 'date'] — unchanged
toSpliced() works like splice() but returns a new array instead of mutating the original. Supported in all modern browsers since 2024.
Also Read: Framework vs Library – Key Differences Explained 2026
Performance Comparison: Which Method is Fastest?
Performance differences are negligible for small arrays (under 1,000 elements). They become significant at scale.
How each method performs
| Method | Mutates original? | Time complexity | Best for |
|---|---|---|---|
| unshift() | Yes | O(n) | Simple scripts, small arrays |
| Spread [x, …arr] | No (new array) | O(n) | React state, immutable patterns |
| concat() | No (new array) | O(n) | Classic immutable, backward compatibility |
| splice(0, 0, x) | Yes | O(n) | Inserting at any position |
| toSpliced(0, 0, x) | No (new array) | O(n) | Modern immutable, ES2023+ |
All prepend methods are O(n) because every existing element must shift. There is no way to avoid this with standard JavaScript arrays.
Why O(n) matters
// For 100 elements — negligible (microseconds)
const small = new Array(100).fill(0);
small.unshift(-1); // Shifts 100 elements — instant
// For 1,000,000 elements — noticeable (milliseconds)
const large = new Array(1_000_000).fill(0);
large.unshift(-1); // Shifts 1,000,000 elements — ~5-15ms
// For 10,000,000 elements — significant (hundreds of ms)
const huge = new Array(10_000_000).fill(0);
huge.unshift(-1); // Shifts 10,000,000 elements — ~50-150ms
Browser engine differences
Different JavaScript engines optimise array operations differently:
- V8 (Chrome, Node.js, Edge): Optimises unshift() for arrays under 64 elements by using a different internal representation. Beyond that, standard shifting applies.
- SpiderMonkey (Firefox): concat() performs slightly faster than spread for arrays over 50,000 elements.
- JavaScriptCore (Safari): Competitive across all methods, with notable optimisation for unshift() in Safari 15+.
For most applications, these differences are not worth optimising for. Choose the method that makes your code clearest.
Framework Patterns: React and Vue
React: immutability is required
React’s virtual DOM detects changes by comparing references. If you mutate an array, React does not see the change. You must create a new array.
import React, { useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState(['Buy groceries', 'Walk the dog']);
const addTodoToTop = (newTodo) => {
// CORRECT: creates a new array, React sees the change
setTodos([newTodo, ...todos]);
};
const addTodoWrong = (newTodo) => {
// WRONG: mutates existing array, React does NOT re-render
todos.unshift(newTodo);
setTodos(todos); // Same reference — React ignores this
};
return (
<div>
<button onClick={() => addTodoToTop('New task')}>Add to top</button>
<ul>
{todos.map((todo, index) => (
<li key={index}>{todo}</li>
))}
</ul>
</div>
);
}
Why the wrong version fails: todos.unshift(newTodo) mutates the existing array. When you call setTodos(todos), you are passing the same array reference. React compares references with === and sees that the reference hasn’t changed, so it skips re-rendering.
Vue: reactivity handles mutation, but immutable is still cleaner
Vue’s reactivity system can detect array mutations through unshift(), but the immutable pattern is recommended for consistency:
// Vue 3 Composition API
import { ref } from 'vue';
const todos = ref(['Buy groceries', 'Walk the dog']);
// Both work in Vue, but the spread is cleaner
// Option A: Mutation (Vue detects this)
todos.value.unshift('New task');
// Option B: Immutable (recommended)
todos.value = ['New task', ...todos.value];
TypeScript patterns
// Type-safe prepending with generics
function prepend<T>(array: readonly T[], ...items: T[]): T[] {
return [...items, ...array];
}
const numbers: readonly number[] = [2, 3, 4];
const updated = prepend(numbers, 0, 1);
// updated: number[] = [0, 1, 2, 3, 4]
// numbers remain [2, 3, 4] — readonly enforced
Optimizing for Large Arrays in Enterprise Applications
When your application handles arrays with tens of thousands of elements and frequent prepending is required, standard array methods become a performance bottleneck. Here are practical strategies:
Strategy 1: Use a reverse buffer
Instead of prepending to the main array, push to a separate buffer and merge periodically:
class PrependBuffer {
constructor() {
this.mainArray = [];
this.buffer = [];
this.bufferLimit = 100;
}
addToFront(item) {
this.buffer.push(item); // O(1) — fast
if (this.buffer.length >= this.bufferLimit) {
this.flush();
}
}
flush() {
// Merge buffer (reversed) with main array — one O(n) operation
// instead of 100 separate O(n) operations
this.mainArray = [...this.buffer.reverse(), ...this.mainArray];
this.buffer = [];
}
getAll() {
if (this.buffer.length > 0) {
return [...this.buffer.reverse(), ...this.mainArray];
}
return this.mainArray;
}
}
// Usage
const list = new PrependBuffer();
list.addToFront('item1'); // O(1)
list.addToFront('item2'); // O(1)
list.addToFront('item3'); // O(1)
// ... 97 more additions — all O(1)
// On the 100th addition, flush() merges once — one O(n) operation
// 100 prepends cost: 99 × O(1) + 1 × O(n) — much cheaper than 100 × O(n)
Strategy 2: Use a linked list for frequent prepending
If your use case requires frequent prepending and you rarely access elements by index, a linked list prepends in O(1):
class Node {
constructor(value, next = null) {
this.value = value;
this.next = next;
}
}
class LinkedList {
constructor() {
this.head = null;
this.size = 0;
}
prepend(value) {
this.head = new Node(value, this.head); // O(1) — always constant time
this.size++;
}
toArray() {
const result = [];
let current = this.head;
while (current) {
result.push(current.value);
current = current. next;
}
return result;
}
}
const list = new LinkedList();
list.prepend('third');
list.prepend('second');
list.prepend('first');
console.log(list.toArray()); // ['first', 'second', 'third']
Strategy 3: Pagination and windowing for UI lists
For UI applications displaying large lists, only keep the visible items in memory:
// Keep a window of recent items instead of prepending to a massive array
class WindowedList {
constructor(maxSize = 1000) {
this.items = [];
this.maxSize = maxSize;
}
addToFront(item) {
this.items = [item, ...this.items.slice(0, this.maxSize - 1)];
// Array never grows beyond maxSize — performance stays constant
}
}
const recentActivity = new WindowedList(500);
recentActivity.addToFront({ action: 'login', time: Date.now() });
// Only keeps the 500 most recent items — old items drop off automatically
For enterprise applications handling large datasets with Ext JS data grids, the grid’s built-in store handles array operations and virtual scrolling internally: you work with the store API, and the framework manages the underlying data structure for optimal performance.
Browser Compatibility Table
All methods covered in this article work across modern browsers:
| Method | Chrome | Firefox | Safari | Edge | Node.js |
|---|---|---|---|---|---|
| unshift() | All versions | All versions | All versions | All versions | All versions |
| Spread [x, …arr] | 46+ | 16+ | 8+ | 12+ | 6+ |
| concat() | All versions | All versions | All versions | All versions | All versions |
| splice() | All versions | All versions | All versions | All versions | All versions |
| toSpliced() | 110+ | 115+ | 16+ | 110+ | 20+ |
| with() | 110+ | 115+ | 16+ | 110+ | 20+ |
For applications that need to support older browsers, unshift(), concat(), and splice() are universally safe. The spread operator requires ES6 support (2015+). toSpliced() and with() require ES2023 support; use a polyfill or transpiler if targeting older environments.
Frequently Asked Questions
What is the most efficient way to add an element to the beginning of a JavaScript array?
For mutable operations, unshift() is the simplest and fastest. For immutable operations, the spread operator [newItem, …array] is the modern standard. All prepend methods have O(n) time complexity because existing elements must shift.
What is the difference between unshift() and push()?
push() adds to the end of the array in O(1) time: no shifting needed. unshift() adds to the beginning in O(n) time: every existing element shifts right. For performance-critical code, push() is significantly faster on large arrays.
Does unshift() modify the original array?
Yes, unshift() mutates the original array in place and returns the new length. If you need immutability, use the spread operator or concat() instead.
How do I add to the beginning of an array in React?
Use the spread operator: setItems([newItem, …items]). Never use unshift() in React — it mutates the array, and React will not detect the change.
Can I add multiple elements to the beginning at once?
Yes. unshift(‘a’, ‘b’, ‘c’) adds all three to the beginning. With spread: [’a’, ‘b’, ‘c’, …existingArray]. Elements are inserted in the order they are passed.
What is toSpliced() and when should I use it?
toSpliced() is an ES2023 method that works like splice() but returns a new array instead of mutating the original. Use it when you want the flexibility of splice() with immutability. Supported in all modern browsers since 2024.
Why is prepending slower than appending?
Prepending requires shifting every existing element one position to the right — O(n) operations. Appending just places the new element at the end — O(1). For an array with 1 million elements, prepending moves 1 million elements; appending moves zero.
How do I prepend in TypeScript with type safety?
Use generics: function prepend<T>(arr: readonly T[], item: T): T[] { return [item, …arr]; }. The readonly modifier prevents accidental mutation of the input array.
What is the best approach for prepending to very large arrays?
For arrays over 100,000 elements with frequent prepending, consider a reverse buffer pattern (batch prepends), a linked list (O(1) prepend), or a windowed list (cap the array size). Standard array methods become noticeably slow at this scale.
How does Ext JS handle large array operations in data grids?
Ext JS data stores manage large datasets through virtual scrolling and buffered rendering. Instead of manipulating raw arrays, you work with the store API, which optimises data operations internally. This approach handles hundreds of thousands of rows without the performance penalties of manual array manipulation.
Is the spread operator faster than concat() for prepending?
Performance is nearly identical in modern browsers. The spread operator is slightly faster in V8 (Chrome/Node.js) for small arrays, while concat() has a slight edge in SpiderMonkey (Firefox) for very large arrays. The difference is negligible for most applications — choose whichever reads better in your codebase.
Conclusion
Adding elements to the beginning of a JavaScript Framework array is a fundamental operation with multiple implementation options. The best choice depends on your specific requirements:
For small arrays and simple scripts, unshift() provides a straightforward solution with minimal overhead. When immutability matters, particularly in React, Vue, or functional programming contexts, the spread operator offers clean syntax that clearly communicates intent. For enterprise applications handling large datasets, understanding the O(n) time complexity helps you make informed architectural decisions about when to use standard arrays and when to consider alternative data structures.
The ES2023 additions (toSpliced(), with()) continue JavaScript’s trend toward immutable-by-default patterns, giving developers more tools for writing predictable, bug-resistant code.
For enterprise applications where array operations happen within complex data grids and large datasets, Ext JS 8.0 provides store-based data management that handles these performance considerations internally, letting you focus on application logic instead of data structure optimization.
In the modern web development landscape, the ability to leverage existing solutions is a superpower.…
This guide covers the technical steps for upgrading Ext JS 7.x applications to 8.0. Upgrading…
This guide covers the technical steps for upgrading Ext JS 6.x applications to 8.0. Upgrading…



