V8 Arrays and Objects Internals

Packed Arrays and Hidden Classes Make Your Constants Huge or Tiny

V8 Arrays and Objects Internals

V8 uses hidden classes, packed vs holey array representations, and SMI tagging to optimise property access — understanding these changes your algorithmic constants without changing Big-O.

6 min read Level 2/5 #dsa#v8#arrays
What you'll learn
  • Explain what hidden classes are and when V8 creates a new one
  • Distinguish packed, holey, and dictionary-mode arrays in V8
  • Write code that stays in V8's fast path for arrays and objects

Two algorithms can share the same Big-O yet run 10× apart on real data. The difference is often not the algorithm — it is the V8 representation chosen for the data. V8 makes aggressive assumptions about objects and arrays. Break those assumptions and you pay a penalty in hidden overhead.

Hidden Classes

When you create an object literal, V8 internally assigns it a hidden class (also called a “shape” or “map”). The hidden class encodes which properties exist and in what order. V8 can then use a compact struct layout and fast property lookups.

// V8 assigns one hidden class here
const point = { x: 0, y: 0 };

// Adding a property AFTER creation creates a new hidden class
point.z = 0; // shape transition — V8 must update the object's class

Every shape transition is cheap alone but causes megamorphic deopt if you add properties in varying orders across many objects. Keep object shapes stable:

// Good — all objects share one hidden class
function makePoint(x, y) {
  return { x, y };
}

// Bad — the object starts with one shape, then transitions
function makePointBad(x, y) {
  const p = {};
  p.x = x; // new hidden class
  p.y = y; // another transition
  return p;
}

SMIs — Small Integers

V8 uses a tagged-pointer representation. Integers that fit in 31 bits on 32-bit builds (or 32 bits on 64-bit builds) are stored as SMIs (Small Integers) — no heap allocation, no boxing. Floating-point numbers and large integers are heap-allocated HeapNumber objects.

const a = 42;      // SMI — no allocation
const b = 42.5;    // HeapNumber — heap allocation
const c = 2 ** 32; // too large for SMI — HeapNumber

A tight numeric loop over an array of SMIs runs significantly faster than one over floats because V8 avoids pointer chasing.

Packed vs Holey Arrays

V8 tracks whether an array has gaps. A packed array has contiguous elements with no undefined holes. A holey array has at least one hole.

const packed = [1, 2, 3];           // PACKED_SMI_ELEMENTS
const holey = [1, , 3];             // HOLEY_SMI_ELEMENTS — the gap is a hole
const alsoHoley = new Array(3);     // HOLEY_ELEMENTS — pre-sized, uninitialized

// Deleting an element punches a hole
const arr = [1, 2, 3];
delete arr[1];                       // now holey — avoid delete on arrays

Packed arrays allow V8 to skip per-element prototype chain checks. Holey arrays must check each slot against undefined and the prototype. The difference is measurable in tight loops.

V8 element kinds, from fastest to slowest:

Element KindDescriptionTrigger
PACKED_SMI_ELEMENTSDense, all small integers[1, 2, 3]
PACKED_DOUBLE_ELEMENTSDense, all doubles[1.1, 2.2]
PACKED_ELEMENTSDense, mixed types[1, "a"]
HOLEY_SMI_ELEMENTSGaps, integers[1,,3]
HOLEY_DOUBLE_ELEMENTSGaps, doubles[1.5,,3.5]
HOLEY_ELEMENTSGaps, mixed[1,,"a"]
DICTIONARY_ELEMENTSSparse hash mapVery large indices or delete

Transitions go downward — once holey, always holey for that array instance.

Dictionary-Mode Objects and Arrays

If an array becomes very sparse (e.g., arr[999999] = 1 on an empty array) or an object accumulates too many properties at runtime, V8 switches to dictionary mode — a hash map internally. Hash map access is slower than struct-style property access.

// Triggers dictionary mode on the array
const sparse = [];
sparse[0] = 1;
sparse[9999] = 2; // huge index gap — V8 may use a hash map internally

// Use a Map instead for sparse key-value storage
const map = new Map();
map.set(0, 1);
map.set(9999, 2); // intentionally a hash map — no surprise transition

Up Next

How V8 handles values internally connects directly to the deeper question of value vs reference semantics — how JS copies, compares, and mutates data.

Primitives vs References →