README
22.2 Hidden Classes and Inline Caching
Understanding hidden classes and inline caching is crucial for writing high-performance JavaScript. These V8 optimizations are key to fast property access.
Table of Contents
- β’Object Representation in V8
- β’Hidden Classes (Shapes/Maps)
- β’Hidden Class Transitions
- β’Inline Caching (IC)
- β’IC States: Monomorphic, Polymorphic, Megamorphic
- β’Optimization Patterns
- β’Anti-Patterns to Avoid
Object Representation in V8
JavaScript objects are dynamicβproperties can be added or removed at any time. But V8 needs to make property access fast. The solution: Hidden Classes.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β OBJECT REPRESENTATION β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β NAIVE APPROACH (Slow): β
β βββββββββββββββββββββ β
β JavaScript Object β Dictionary/Hash Map β
β β
β { x: 10, y: 20 } β
β β β
β βββββββββββββββββββ β
β β Hash Map β β Each property lookup requires: β
β β "x" β 10 β 1. Compute hash of "x" β
β β "y" β 20 β 2. Find bucket β
β βββββββββββββββββββ 3. Handle collisions β
β 4. Return value β
β β
β V8 APPROACH (Fast): β
β ββββββββββββββββββ β
β JavaScript Object β Hidden Class + Fixed-Layout Storage β
β β
β { x: 10, y: 20 } β
β β β
β ββββββββββββββββ ββββββββββββββββββββββ β
β β Hidden Class βββββββ Object Storage β β
β β x @ offset 0 β β [0]: 10 (x) β β
β β y @ offset 8 β β [8]: 20 (y) β β
β ββββββββββββββββ ββββββββββββββββββββββ β
β β
β Property access: Just read from known offset! β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Hidden Classes (Shapes/Maps)
Hidden classes (also called "shapes" or "maps") describe the structure of objects.
What Hidden Classes Store
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HIDDEN CLASS STRUCTURE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Hidden Class for { x: number, y: number } β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Property Name β Offset β Type Hint β Attributes β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β "x" β 0 β SMI/Number β Writable β β
β β "y" β 8 β SMI/Number β Writable β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β Additional Metadata: β
β β’ Prototype reference β
β β’ Constructor reference β
β β’ Transition table (for adding properties) β
β β’ Number of properties β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Objects Sharing Hidden Classes
// These objects share the SAME hidden class
const point1 = { x: 10, y: 20 };
const point2 = { x: 30, y: 40 };
const point3 = { x: 50, y: 60 };
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SHARED HIDDEN CLASS β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββββββ β
β β Hidden Class β β
β β x @ offset 0 β β
β β y @ offset 8 β β
β ββββββββββ¬βββββββββ β
β β β
β βββββββββββββββΌββββββββββββββ β
β β β β β
β βββββββββββββ βββββββββββββ βββββββββββββ β
β β point1 β β point2 β β point3 β β
β β [0]: 10 β β [0]: 30 β β [0]: 50 β β
β β [8]: 20 β β [8]: 40 β β [8]: 60 β β
β βββββββββββββ βββββββββββββ βββββββββββββ β
β β
β Benefits: β
β β’ Memory efficient (one hidden class for many objects) β
β β’ Enables inline caching (same access pattern) β
β β’ Fast property access (known offsets) β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Hidden Class Transitions
When you add a property to an object, V8 transitions to a new hidden class.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HIDDEN CLASS TRANSITIONS β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β const point = {}; // Hidden Class C0 (empty) β
β point.x = 10; // Transition to C1 (has x) β
β point.y = 20; // Transition to C2 (has x, y) β
β β
β Transition Chain: β
β β
β ββββββββββββ add 'x' ββββββββββββ add 'y' ββββββββββββ β
β β C0 β βββββββββ β C1 β βββββββββ β C2 β β
β β (empty) β β x @ 0 β β x @ 0 β β
β ββββββββββββ ββββββββββββ β y @ 8 β β
β ββββββββββββ β
β β
β Transition Tree (for property additions): β
β β
β C0 (empty) β
β / \ β
β add 'x' add 'y' β
β β β β
β C1 C3 β
β (x @ 0) (y @ 0) β
β | | β
β add 'y' add 'x' β
β β β β
β C2 C4 β
β (x @ 0, y @ 8) (y @ 0, x @ 8) β
β β
β β οΈ C2 β C4: Different hidden classes for same properties! β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Property Order Matters!
// These create DIFFERENT hidden classes
function createPointA() {
const p = {};
p.x = 0; // First
p.y = 0; // Second
return p;
}
function createPointB() {
const p = {};
p.y = 0; // First (different order!)
p.x = 0; // Second
return p;
}
const a = createPointA(); // Hidden class: x@0, y@8
const b = createPointB(); // Hidden class: y@0, x@8
// These are incompatible for inline caching!
Inline Caching (IC)
Inline caching remembers how property access worked last time to speed up future access.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β INLINE CACHING β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β function getX(obj) { β
β return obj.x; // β Property access site β
β } β
β β
β WITHOUT INLINE CACHING (Slow): β
β βββββββββββββββββββββββββββββββ β
β Every call: β
β 1. Look up "x" in obj's hidden class β
β 2. Find offset β
β 3. Load value from offset β
β β
β WITH INLINE CACHING (Fast): β
β ββββββββββββββββββββββββββββ β
β First call: β
β 1. Look up "x" in obj's hidden class β offset 0 β
β 2. CACHE: "For hidden class C2, x is at offset 0" β
β 3. Load value β
β β
β Subsequent calls with same hidden class: β
β 1. Check: Is obj's hidden class C2? Yes! β
β 2. Load directly from offset 0 β FAST PATH β
β β
β Generated Code Evolution: β
β β
β // Uninitialized β
β obj.x β GenericPropertyLoad("x") β
β β
β // After first call (Monomorphic) β
β obj.x β if (obj.map === C2) LoadField(0) else Slow() β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
IC States
Inline caches have different states based on how many different object shapes they've seen.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β IC STATES β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β 1. UNINITIALIZED β
β β’ Property access has never executed β
β β’ Will transition on first execution β
β β
β 2. MONOMORPHIC β (Fastest) β
β β’ Only seen ONE hidden class β
β β’ Direct memory access β
β β’ Single comparison check β
β β
β 3. POLYMORPHIC (Still fast) β
β β’ Seen 2-4 different hidden classes β
β β’ Small lookup table β
β β’ Linear search through known shapes β
β β
β 4. MEGAMORPHIC β οΈ (Slow) β
β β’ Seen 5+ different hidden classes β
β β’ Falls back to dictionary lookup β
β β’ No caching benefit β
β β
β Transition Diagram: β
β β
β UNINITIALIZED ββ(first call)βββ MONOMORPHIC β
β β β
β (new shape) β β
β β β
β POLYMORPHIC β
β β β
β (5+ shapes) β β
β β β
β MEGAMORPHIC β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
IC State Examples
function getX(obj) {
return obj.x;
}
// Monomorphic - FAST β
const points = [];
for (let i = 0; i < 1000; i++) {
points.push({ x: i, y: i * 2 }); // All same shape
}
points.forEach((p) => getX(p)); // IC stays monomorphic
// Polymorphic - Still OK
function getX2(obj) {
return obj.x;
}
getX2({ x: 1, y: 2 }); // Shape A
getX2({ x: 1, y: 2, z: 3 }); // Shape B
getX2({ x: 1 }); // Shape C
// IC is polymorphic (3 shapes)
// Megamorphic - SLOW β οΈ
function getX3(obj) {
return obj.x;
}
getX3({ x: 1 });
getX3({ x: 1, a: 2 });
getX3({ x: 1, b: 2 });
getX3({ x: 1, c: 2 });
getX3({ x: 1, d: 2 });
getX3({ x: 1, e: 2 });
// IC becomes megamorphic (6+ shapes)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β IC STATE PERFORMANCE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Performance Comparison (relative): β
β β
β MONOMORPHIC ββββββββββββββββββββββββββββββββ 100% β
β POLYMORPHIC ββββββββββββββββββββ 60-80% β
β MEGAMORPHIC ββββββββ 20-30% β
β β
β Generated Code: β
β β
β MONOMORPHIC: β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β cmp [obj + map_offset], expected_map β β
β β jne slow_path β β
β β mov result, [obj + 0] ; Direct load! β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β POLYMORPHIC (3 shapes): β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β mov map, [obj + map_offset] β β
β β cmp map, map1; je load_at_0 β β
β β cmp map, map2; je load_at_8 β β
β β cmp map, map3; je load_at_16 β β
β β jmp slow_path β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β MEGAMORPHIC: β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β call GenericPropertyLookup ; Dictionary lookup β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Optimization Patterns
Pattern 1: Consistent Object Initialization
// β
GOOD: All objects have same hidden class
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
// Factory function with consistent shape
function createPoint(x, y) {
return { x, y }; // Always same order
}
// β
GOOD: All properties initialized upfront
function createUser(name, age) {
return {
name: name,
age: age,
email: null, // Initialize even if null
active: false, // Initialize even if false
score: 0, // Initialize even if 0
};
}
Pattern 2: Initialize All Properties in Constructor
// β BAD: Properties added later
class Person {
constructor(name) {
this.name = name;
// age added conditionally later
}
setAge(age) {
this.age = age; // Hidden class transition!
}
}
// β
GOOD: All properties in constructor
class Person {
constructor(name, age = null) {
this.name = name;
this.age = age; // Always present
}
}
Pattern 3: Avoid Dynamic Property Names
// β BAD: Dynamic property names
function setProperty(obj, key, value) {
obj[key] = value; // Each key creates new hidden class
}
const obj = {};
setProperty(obj, 'a', 1);
setProperty(obj, 'b', 2);
setProperty(obj, 'c', 3);
// β
GOOD: Use Map for dynamic keys
const data = new Map();
data.set('a', 1);
data.set('b', 2);
data.set('c', 3);
Pattern 4: Monomorphic Functions
// β BAD: Called with different shapes
function processItem(item) {
return item.value * 2;
}
processItem({ value: 1 });
processItem({ value: 2, extra: 3 });
processItem({ other: 0, value: 4 });
// β
GOOD: Consistent input shapes
function processNumber(num) {
return num * 2;
}
processNumber(1);
processNumber(2);
processNumber(4);
Anti-Patterns to Avoid
Anti-Pattern 1: Deleting Properties
// β BAD: delete causes hidden class transition
const user = { name: 'Alice', age: 25, temp: 'data' };
delete user.temp; // Transitions to "dictionary mode"!
// β
GOOD: Set to null/undefined instead
const user = { name: 'Alice', age: 25, temp: 'data' };
user.temp = null; // Keeps same hidden class
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DELETE PROPERTY EFFECT β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Before delete: β
β ββββββββββββββββ βββββββββββββββββ β
β β Hidden Class βββββββ Fast Object β β
β β name @ 0 β β [0]: "Alice" β β
β β age @ 8 β β [8]: 25 β β
β β temp @ 16 β β [16]: "data" β β
β ββββββββββββββββ βββββββββββββββββ β
β β
β After delete user.temp: β
β ββββββββββββββββββββ βββββββββββββββββββββββ β
β β Dictionary Mode βββββββ Slow Hash Table β β
β β (No fixed shape) β β "name" β "Alice" β β
β β β β "age" β 25 β β
β ββββββββββββββββββββ βββββββββββββββββββββββ β
β β
β β οΈ Object permanently becomes slow! β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Anti-Pattern 2: Inconsistent Property Order
// β BAD: Different property order
function createResponseA(success, data) {
return { success, data }; // success first
}
function createResponseB(data, success) {
return { data, success }; // data first
}
// β
GOOD: Consistent order everywhere
function createResponse(success, data) {
return { success, data }; // Always same order
}
Anti-Pattern 3: Modifying Prototypes
// β BAD: Modifying built-in prototypes
Array.prototype.sum = function () {
return this.reduce((a, b) => a + b, 0);
};
// This invalidates optimized code!
// β
GOOD: Use helper functions
function sum(arr) {
return arr.reduce((a, b) => a + b, 0);
}
Anti-Pattern 4: Adding Properties Outside Constructor
// β BAD: Properties added after construction
class Widget {
constructor() {
this.id = Math.random();
}
init() {
this.width = 100; // New hidden class
this.height = 100; // Another transition
}
}
// β
GOOD: All properties in constructor
class Widget {
constructor() {
this.id = Math.random();
this.width = 0; // Initialize all
this.height = 0;
}
init() {
this.width = 100; // Same hidden class
this.height = 100;
}
}
Debugging Hidden Classes
Using Chrome DevTools
// In Chrome DevTools Console:
// 1. Take a heap snapshot
// 2. Look for objects with same constructor
// 3. Check if they have same "map" (hidden class)
// Using V8 native syntax (Node.js with --allow-natives-syntax):
// %HaveSameMap(obj1, obj2) // Returns true if same hidden class
Using Node.js Flags
# Trace IC state changes
node --trace-ic your-script.js
# Trace hidden class transitions
node --trace-maps your-script.js
# Trace deoptimizations
node --trace-deopt your-script.js
Key Takeaways
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HIDDEN CLASSES & IC SUMMARY β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β HIDDEN CLASSES: β
β β’ Objects with same shape share hidden classes β
β β’ Property order matters! β
β β’ Adding/deleting properties creates transitions β
β β’ Initialize all properties upfront β
β β
β INLINE CACHING: β
β β’ Caches property access patterns β
β β’ Monomorphic = fastest (one shape) β
β β’ Polymorphic = OK (2-4 shapes) β
β β’ Megamorphic = slow (5+ shapes) β
β β
β OPTIMIZATION TIPS: β
β β
Use classes/factories for consistent shapes β
β β
Initialize all properties in constructors β
β β
Keep property order consistent β
β β
Use null instead of delete β
β β
Use Map for truly dynamic keys β
β β
β AVOID: β
β β delete operator β
β β Adding properties after initialization β
β β Different property orders for same type β
β β Modifying prototypes at runtime β
β β Too many object shapes in one function β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Next Steps
Continue to 22.3 Advanced Garbage Collection to understand how V8 manages memory with generational and incremental garbage collection.