Hidden classes and inline caches

JavaScript · Expert · 15 min

Object shape stability is the single biggest factor in how fast property access is in hot code.

JavaScript objects look like hash maps, but engines represent them much more efficiently. Each object points to a hidden class (V8 calls it a Map, other engines a Shape or Structure) that describes its layout: which properties it has, in what order, and at which offset each value is stored. Objects created the same way share the same hidden class.

Adding a property transitions the object to a new hidden class, and engines cache these transitions: {} → {x} → {x, y}. Two objects get the same shape only if they gain the same properties in the same order — {x, y} and {y, x} have different shapes.

Every property access site in your code has an inline cache (IC) that remembers the shapes it has seen and where the property lives:

  • Monomorphic — one shape: a single check plus a direct memory load. Fastest.
  • Polymorphic — a few shapes (V8 handles up to four): a short chain of checks.
  • Megamorphic — many shapes: falls back to a generic, much slower lookup.

The optimising compiler builds on IC feedback, so megamorphic sites also block good optimisation.

Habits that keep objects fast:

  • Initialise all fields in the constructor (or as class fields), always in the same order. Use null instead of adding a field later.
  • Avoid delete on hot objects; it typically drops the object into slow dictionary mode. Set the field to undefined or build a new object instead.
  • Don't use one object both as a record and as a dynamic dictionary — use a Map for the latter.

Arrays have an analogous system of elements kinds (e.g. packed small integers → doubles → generic; packed → holey). Transitions only go towards more general kinds, so avoid holes and mixed element types in hot arrays.

Example

// Run: node --allow-natives-syntax shapes.mjs
// %HaveSameMap is a V8 debugging intrinsic, not standard JavaScript.

class Point {
  constructor(x, y) { this.x = x; this.y = y; }   // same order, every time
}
const a = new Point(1, 2);
const b = new Point(3, 4);
console.log(%HaveSameMap(a, b));          // true: shared hidden class

const c = { x: 1, y: 2 };
const d = { y: 2, x: 1 };
console.log(%HaveSameMap(c, d));          // false: different insertion order

const e = { x: 1, y: 2 };
e.z = 3;                                  // transition to a new shape
console.log(%HaveSameMap(c, e));          // false

const f = { x: 1, y: 2, z: 3 };
delete f.z;                               // typically -> dictionary mode
console.log(%HasFastProperties(f));       // false

Remember

  • Objects with the same properties added in the same order share a hidden class
  • Inline caches make monomorphic property access nearly free; megamorphic sites are slow
  • Initialise every field in the constructor in a fixed order; avoid delete on hot objects

Watch out for

  • Adding optional properties conditionally after construction, creating many shapes for one 'type'.

Check yourself

  1. Which pair of objects most likely shares a hidden class in V8?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_objects