V8 Packed/Holey and SMI/Double/Object Kinds Determine Real Array Performance
Arrays Deep Dive
JavaScript arrays are not homogeneous blocks of memory — V8 uses several internal representations whose performance characteristics differ dramatically from each other.
What you'll learn
- Distinguish V8 packed vs. holey arrays and SMI/double/object element kinds
- Choose TypedArrays for numeric-heavy workloads that need predictable performance
- Avoid common patterns that silently degrade an array from a fast to a slow kind
A JavaScript array feels like a simple ordered list, but V8 — the engine behind Node.js and Chrome — stores arrays in several very different ways depending on what you put into them. Getting arrays wrong can make tight loops 3–10x slower than necessary.
Element Kinds
V8 tracks the “element kind” of every array. The kinds form a one-way lattice: once an array is demoted to a slower kind it never goes back.
| Kind | Trigger | Speed |
|---|---|---|
| PACKED_SMI_ELEMENTS | All values are small integers, no holes | Fastest |
| PACKED_DOUBLE_ELEMENTS | All values are doubles, no holes | Fast |
| PACKED_ELEMENTS | Mixed values, no holes | Moderate |
| HOLEY_SMI_ELEMENTS | Small integers but with holes | Slower |
| HOLEY_DOUBLE_ELEMENTS | Doubles with holes | Slower |
| HOLEY_ELEMENTS | Mixed values with holes | Slowest |
“Holes” are created by delete arr[i], by allocating new Array(n) (preallocated
but unfilled), or by writing past the current length.
// Starts as PACKED_SMI_ELEMENTS
const a = [1, 2, 3];
// Demoted to PACKED_DOUBLE_ELEMENTS
a.push(1.5);
// Demoted to PACKED_ELEMENTS (objects)
a.push("hello");
// Now holey — never recovers
delete a[0]; Packed vs. Holey
A packed array has no gaps. V8 can iterate it without checking whether each
index is defined, which lets it skip a prototype-chain lookup per element. A
holey array must check every index, falling back to Array.prototype if the
slot is empty — that per-element lookup kills throughput in tight loops.
Practical rules: always fill arrays before use, prefer Array.from or .fill
over new Array(n), and avoid delete on array indices (use splice instead).
TypedArrays
When all values are numbers, TypedArray is the right tool. It uses a true
contiguous block of memory with a fixed element type.
// 32-bit integer array — 4 bytes per element, contiguous
const ints = new Int32Array(1_000_000);
// 64-bit floating-point — same as C double
const floats = new Float64Array([1.1, 2.2, 3.3]);
// Shared backing buffer across two views
const buf = new ArrayBuffer(8);
const i32 = new Int32Array(buf);
const f32 = new Float32Array(buf);
i32[0] = 42;
console.log(f32[0]); // same bytes, different interpretation | Array type | Element size | Backed by | Best for |
|---|---|---|---|
| Regular Array | Variable | Hidden class + properties | Mixed data, sparse lists |
| Int32Array | 4 bytes | ArrayBuffer | Integer math, image pixels |
| Float64Array | 8 bytes | ArrayBuffer | Scientific / financial data |
| Uint8Array | 1 byte | ArrayBuffer | Binary protocols, crypto |
When to Use Which
Use a regular array when elements are mixed type or the size is unknown. Use a
TypedArray when you have a large, homogeneous numeric dataset — the difference
in a tight loop can be measured in microseconds per element.
Up Next
Now that you understand what arrays really are internally, see the Big-O cost of the methods you call on them every day.
Array Method Complexity →