Packed vs Holey Arrays in V8: Why new Array(n) Makes Spread 4x Slower
Two arrays. Same million numbers, in the same order, with no gaps in either: const a = [ ] ; for ( let i = 0 ; i < 1_000_000 ; i ++ ) a . push ( i ) ; const b = new Array ( 1_000_000 ) ; for ( let i = 0 ; i < 1_000_000 ; i ++ ) b [ i ] = i ; Copy each one with spread: [...a]: 0.17 ms [...b]: 0.71 ms Same contents, same length, and b takes four times as long to copy. The difference is how b…
The article compares the performance of two types of arrays in V8: packed arrays and holey arrays. A packed array has no gaps and can be copied quickly using spread syntax, like [...a] taking only 0.17 milliseconds to copy a million-element array. On the other hand, a holey array has gaps and takes significantly longer to copy with spread syntax. For example, [...b] takes 0.71 milliseconds to copy the same million-element array, which is four times slower than the packed array.
The reason for the performance difference lies in how V8 stores and processes arrays. V8 puts a label on each array's storage box, describing what is inside and whether there may be gaps (packed or holey). Once the label is set, it cannot be changed, even if the contents of the array are modified. There are three content labels in V8: SMI (small whole numbers), DOUBLE (any numbers), and ELEMENTS (anything). Each content label has both packed and holey versions, resulting in six possible labels for a plain array.
Creating a million-element array with new Array(1,000,000) does not fill it with undefined values; instead, it creates a million holes, which are empty slots. Filling these holes does not change the array's label because V8 won't scan a million slots to check for gaps. The label describes the array's history, not its contents, and the label can become more general (from packed to holey) but never less general.
To build arrays efficiently and avoid the performance penalty associated with holey arrays, it is recommended to fill the array from front to back, avoid deleting items, and keep number arrays numeric. Building arrays with push, map, filter, or Array.from({ length: n }, fn) will result in packed arrays, which are faster to process. In hot paths, such as data grids, charts, parsers, and game loops, these optimizations can significantly improve performance.
In summary, V8 labels every array based on what it holds and whether it has gaps. These labels are set once and can only become more general, meaning that holey arrays cannot be turned into packed arrays even after filling in all gaps. To keep arrays fast, build them with push, map, or Array.from, avoid deletions, and refrain from mixing types within number arrays.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.