When optimizing JavaScript code, many developers assume that pre-allocating an array with a fixed size should always be faster than dynamically adding elements using push(). This assumption comes from lower-level programming languages where memory allocation is expensive.
However, in modern JavaScript engines such as Google’s V8 (used by Chrome and Node.js), dynamic pushing can sometimes outperform pre-allocation. Understanding why requires a look at how JavaScript engines optimize arrays internally.
The Two Approaches
Pre-Allocation
const arr = new Array(1000000);
for (let i = 0; i < 1000000; i++) {
arr[i] = i;
}
Dynamic Push
const arr = [];
for (let i = 0; i < 1000000; i++) {
arr.push(i);
}
At first glance, pre-allocation appears more efficient because memory is reserved upfront. Surprisingly, benchmarks often show that push() performs similarly or even better.
How JavaScript Arrays Actually Work
Unlike arrays in languages such as C or Java, JavaScript arrays are highly optimized dynamic structures.
JavaScript engines maintain several internal representations of arrays based on:
- Data type consistency
- Array density
- Presence of holes (empty slots)
- Array growth patterns
The engine continuously optimizes and de-optimizes arrays depending on how they are used.
Pre-Allocated Arrays Contain Holes
When you create:
const arr = new Array(1000000);
the array does not actually contain one million values.
Instead, it contains one million empty slots.
console.log(arr[0]); // undefined
console.log(0 in arr); // false
These “holes” force the engine to use a less optimized internal representation.
As a result:
- Additional checks are required during access
- Optimizations may be limited
- Memory handling becomes more complex
Dynamic Push Creates Dense Arrays
When using:
const arr = [];
arr.push(1);
arr.push(2);
arr.push(3);
the engine sees a predictable growth pattern.
The array remains dense, meaning every index contains a valid value.
Dense arrays allow JavaScript engines to:
- Use optimized storage formats
- Reduce lookup overhead
- Apply aggressive JIT optimizations
- Improve cache efficiency
This often leads to faster execution.
JavaScript Engines Already Optimize Growth
Modern engines don’t increase array capacity one element at a time.
Instead, they allocate extra space behind the scenes.
For example:
- Capacity starts at 16
- Grows to 32
- Then 64
- Then 128
This exponential growth strategy minimizes expensive reallocations.
As a result, the traditional argument that push() repeatedly reallocates memory is mostly irrelevant in modern JavaScript.
Hidden Class and Elements-Kind Optimizations
V8 tracks array structure using internal metadata known as elements kinds.
An array created with:
const arr = [];
and filled using:
arr.push(i);
can stay in a highly optimized packed state.
A pre-allocated array often starts as a holey array:
const arr = new Array(1000);
The engine must treat it differently because missing values are possible.
Packed arrays generally receive better optimization than holey arrays.
Benchmark Example
console.time("push");
const pushArr = [];
for (let i = 0; i < 1000000; i++) {
pushArr.push(i);
}
console.timeEnd("push");
console.time("preallocate");
const preArr = new Array(1000000);
for (let i = 0; i < 1000000; i++) {
preArr[i] = i;
}
console.timeEnd("preallocate");
Depending on the JavaScript engine and hardware, results may show:
push: 12ms
preallocate: 16ms
or very similar timings.
The exact numbers vary, but dynamic push frequently performs surprisingly well.
When Pre-Allocation Can Still Help
Pre-allocation is not always slower.
It can be beneficial when:
- Array size is known exactly
- The array is filled immediately
- The workload is extremely large
- The engine can optimize the specific access pattern
Performance should always be measured rather than assumed.
The Real Lesson
Many JavaScript performance myths originate from lower-level languages.
Modern JavaScript engines contain sophisticated optimizations that often make intuitive assumptions incorrect.
In many real-world scenarios:
arr.push(value);
is just as fast—or faster—than manually pre-allocating array space.
Rather than relying on theoretical optimizations, benchmark your specific use case and trust the JavaScript engine’s highly optimized array implementation.
Infographic

Conclusion
Pre-allocating arrays may seem like a performance win, but modern JavaScript engines often favor dynamically growing arrays through push(). Pre-allocated arrays begin as holey arrays, which can limit optimizations, while dynamically pushed arrays remain dense and highly optimized.
For most applications, clean and readable code using push() is the best choice unless profiling proves otherwise.