Skip to main content

Article

JavaScript Array Internals: Memory Layout Without the Myths

JavaScript arrays are objects with specialized element storage: engines may pack dense indexed elements contiguously, but holes, mixed types, and properties force more general representations.

Author
Mohammed Abdelhady
Published
Updated
Reading time
12 min read
Dense array slots transitioning through holes and mixed elements into keyed storage
A project-owned visual metaphor for packed, holey, and dictionary-style array storage.

Opening answer

A JavaScript Array is specified as an exotic object with special handling for the length property and indexed elements (ECMAScript — Array Exotic Objects). Engines then apply representation optimizations: dense numeric sequences may be stored in packed element stores; holes, attribute changes, or mixed types push the engine toward more general (and usually slower) layouts.

There is no single “C array” guaranteed by the language. There are spec semantics plus engine element kinds.

Spec semantics vs engine storage

Spec-level facts

  • Arrays are objects; indices are string keys with special length behavior.
  • length is constrained and interacts with adding/removing indexed properties.
  • Holes (empty slots, not the value undefined) are real: new Array(3) is not the same as [undefined, undefined, undefined].

MDN’s Array documentation covers the developer-facing behavior that follows from the specification.

Engine-level optimizations (V8-oriented mental model)

Modern engines detect common patterns and choose specialized element storage. V8 literature discusses property and element storage strategies (see Fast properties for the broader “fast object” philosophy). Exact element-kind names and transitions are implementation details and must not be treated as language guarantees.

A useful conceptual spectrum:

packed SMI / packed double / packed elements
        ↓ (holes, deletes, hard transitions)
holey variants
        ↓ (attributes, prototype tricks, extremes)
dictionary / slow modes

When people say “the array became holey,” they mean the engine no longer treats the indexed range as a dense packed store.

Scroll horizontally to explore this diagram.

Three engine-dependent conceptual array representations. Packed storage has consecutive values at indices zero through three; holey storage marks index one as absent rather than undefined; sparse dictionary-style storage maps a few distant numeric indices to values.
Representation is an engine choice. The figure contrasts a dense packed store, a holey store with an absent index, and a sparse dictionary-style map. ECMAScript does not mandate these layouts.

Read the three panels as possible optimization models, not a transition guarantee. An engine may specialize consecutive present indices, account for a missing index in a holey representation, or use a map-like strategy for a few widely separated keys. The specification preserves observable array behavior while leaving those storage choices to each engine.

Dense, holey, and sparse patterns

Dense packed

const xs = [1, 2, 3, 4];

Consecutive indices from 0 without holes are the best case for packed storage.

Holes

const ys = [1, , 3]; // hole at index 1
const zs = new Array(1000); // length 1000, no indexed elements yet

Holes force more careful iteration semantics (for vs forEach vs sparse iteration) and often less specialized storage.

Sparse high indices

const sparse = [];
sparse[1000000] = 1;

Creating huge gaps is a classic way to leave “looks like an array” territory and pay object-map costs.

Practical rules (without fake benchmarks)

These rules follow from representation costs and real-world engine behavior. They are guidelines, not stopwatch claims:

  1. Build arrays densely when you can — prefer push into a growing dense array over writing random high indices.
  2. Avoid delete arr[i] for hot arrays — prefer writing undefined only if a hole is acceptable, or rebuild; delete creates holes.
  3. Keep element types stable in hot paths — mixing objects and numbers can force more general element stores.
  4. Do not pre-allocate with new Array(n) unless you immediately fill densely — you start with holes.
  5. Measure with your engine and workload — microbenchmarks lie; use realistic data shapes.

Iteration semantics to respect

const a = [1, , 3];

for (let i = 0; i < a.length; i++) {
  // visits index 1 as undefined
}

a.forEach((value) => {
  // skips holes
});

Choosing the wrong iteration style is a correctness bug first, performance issue second.

Typed arrays are a different tool

When you need true contiguous numeric buffers (binary protocols, WebGL, audio), use TypedArray / ArrayBuffer. They are fixed-type packed memory by design. Ordinary Array remains the flexible dynamic structure for general application data (MDN Array).

Takeaway

JavaScript arrays combine exotic-object semantics with aggressive engine specialization. Dense, type-stable index usage keeps you on faster element representations; holes, deletes, and sparse writes push you toward general object storage. Learn the semantics from the specification and MDN; treat engine element kinds as a performance model to guide structure—not as APIs you code against.

Further reading