V8 Hidden Classes (Shapes) & Inline Caching
- Last updated
- Prerequisites:
- How JavaScript objects and property access work
- Basic familiarity with JIT compilation concepts
- v8
- hidden-classes
- inline-caching
- jit
- shapes
- performance
Read at your depth
The practical view
Every JavaScript object has a 'shape' (hidden class) that records the order and type of its properties. When you create objects with the same keys in the same order, they share one hidden class, and property reads/writes compile to a fast path. This is why consistent object shapes matter: const a = { x: 1, y: 2 }; const b = { x: 3, y: 4 }; are faster than objects whose keys are added one by one. Adding, deleting, or reordering keys creates a new hidden class and breaks the fast path. Keeping object shapes stable across hot loops is a real, measurable performance lever.
The same idea in other frameworks
Object shape consistency matters identically across runtimes — React perf guides push the same stable-shape advice because V8 underlies the browser.Same V8 behavior applies; Angular devs hit this when mutating model objects with dynamic keys in hot change-detection loops.Vue's reactivity already pre-defines all reactive keys via Proxy traps, so shapes stay stable; still, avoid deleting reactive keys.Svelte compiles away much object overhead, but raw object hot loops in runes code still benefit from stable shapes.Legacy vs modern
Stable object shapes vs shape-churning object construction
Consistently shaped objects share hidden classes and hit monomorphic inline caches; objects with changing shapes force new maps and slow dictionary mode.
const obj = {}; // ❌ OLD: start empty, add keys one by one → new hidden class each time
obj.x = 1; obj.y = 2; obj.z = 3; // each add triggers a Map transitionconst obj = { x: 1, y: 2, z: 3 }; // ✅ NEW: all keys at once → single hidden classInterview gotchas
Context
Interviewers use this to test whether you understand that JS property access is not a simple hash lookup but is heavily optimized by V8's shape + inline-cache machinery.
The mechanical answer
When all objects share one hidden class (Map), the property's storage offset is fixed and known. The JIT emits a monomorphic inline cache: a cheap MapCheck (compare the object's Map pointer) followed by a direct load from the fixed offset. When keys are added one at a time, each add creates a new Map via a transition, so structurally identical objects no longer converge on one Map — the load site sees multiple Maps, becomes polymorphic, then megamorphic (past ~4 Maps), and V8 abandons the specialized path for a generic runtime lookup that also consults the descriptor/backing-store indirection. Enough churn can even demote the object to dictionary mode, which is strictly slower for reads.
Trap
The naive answer is 'JavaScript is a dynamic language so every property access is a dictionary lookup'. That ignores V8's shapes and inline caches entirely. Another trap: claiming that adding keys in a different order only costs at construction — the real cost surfaces in the hot read/write sites that lose monomorphism, and in loop iterations that repeatedly pay transition costs.
Context
This probes your knowledge of the IC state machine and the monomorphic → polymorphic → megamorphic degradation, a core V8 scheduling/optimization concept.
The mechanical answer
Every load/store site starts uninitialized. The first Map it sees makes it monomorphic: it stores (Map, offset) and the JIT compiles a MapCheck + direct access. A second distinct Map upgrades it to polymorphic, where the IC keeps a small set (up to ~4 Maps) of (Map, offset) pairs and the generated code branches among them. When a fourth or fifth distinct Map arrives, the site becomes megamorphic and V8 throws away the specialized handlers, falling back to the runtime's generic property access that walks the descriptor array or dictionary each time — significantly slower and no longer inline-cacheable. This is why real-world hot functions that receive many object shapes underperform.
Trap
A common misconception is that the IC stores the property value itself or that polymorphism has no real cost until many hundreds of shapes. In fact the megamorphic cutoff is tiny (~4 Maps), so even a handful of shapes in a hot loop permanently kills the fast path. Also, candidates often forget that the IC is keyed on the hidden class (Map), not on the property name — two different shapes for the same key count as two entries.