Chapter 7.1☕ 20 min

Object Internals: Hidden Classes, Shapes & Inline Caches

Object ka andar ek Shape hota hai — same order, same Shape, Fast. Delete kiya, Dictionary Mode, Slow.

01Hidden Classes (Shapes/Maps): The Object DNA

Every object in V8 has a hidden C++ class called a Map (also known as a Shape or Hidden Class) that describes its structure. The Map tells V8 where properties are stored in memory — their exact offsets, not just their names.

When you add a property, V8 doesn't modify the existing hidden class. It transitions to a NEW hidden class. Objects with the same property structure share the SAME hidden class, making property access incredibly fast and memory efficient.

// Same structure, same Hidden Class (FAST)
const obj1 = {};
obj1.x = 1;
obj1.y = 2;
// V8: Map0 (empty) -> Map1 (x) -> Map2 (x, y)

const obj2 = {};
obj2.x = 3;
obj2.y = 4;
// V8: Reuses Map0 -> Map1 -> Map2! Same shape.

// Different structure, different Hidden Classes (SLOW)
const obj3 = {};
obj3.y = 5; // Map0 -> Map3 (y) -> Map4 (y, x)
obj3.x = 6;
// obj3 has a DIFFERENT hidden class than obj1/obj2!
Transition Trees: V8 creates a 'transition tree' of hidden classes. When you add property 'x' to an empty object, V8 creates Map1. If another empty object also adds 'x', V8 reuses that same Map1. This is why adding properties in the same order is critical — it allows V8 to reuse hidden classes across instances.
02Inline Caches (IC): Memorizing Property Access

Inline Caching (IC) is V8's secret weapon for fast property access. When you read obj.x, V8 doesn't search the object every time — it memorizes where it found the property.

The first time obj.x is accessed, V8 notes: "The last time I read 'x', the object had Map2, and 'x' was at offset 0." Next time, V8 checks if the object's Map is still Map2. If yes — it skips the lookup and goes straight to offset 0. Lightning fast.

function getX(obj) {
  return obj.x; // V8 remembers the hidden class here
}

// Monomorphic: Always same shape -> IC hits every time
const a = { x: 1, y: 2 };
getX(a); // V8 caches: "Map(x,y) -> offset 0"
getX(a); // Cache HIT! Direct memory access.

// Polymorphic: Two different shapes
const b = { x: 1, y: 2, z: 3 };
getX(b); // V8 updates IC: "Check Map(x,y) OR Map(x,y,z)"

const c = { x: 1, w: 4 };
getX(c); // IC becomes polymorphic (3 shapes). Slower.

// Megamorphic: Too many shapes -> V8 gives up
// If you pass 10 different shaped objects to getX,
// the IC becomes megamorphic and uses slow hash lookup.
IC Performance Tiers: Monomorphic (1 shape cached) = FASTEST — direct offset access. Polymorphic (2-4 shapes) = OK — checks a small list of cached shapes. Megamorphic (5+ shapes) = SLOWEST — V8 gives up on caching and falls back to a full dictionary lookup every single time.
03How to Kill Optimization (And How to Fix It)

These patterns kill V8 optimization silently. Your code still works, but it runs significantly slower.

KILLER 1: Adding properties in different orders. Creates different hidden class transition chains. V8 cannot reuse hidden classes across instances.

KILLER 2: Deleting properties. Forces V8 to transition the object to a slow "dictionary mode" where properties are stored in a hash table.

KILLER 3: Mixing types. If obj.x = 1 (number) then obj.x = "string" — V8 must change the hidden class to accept any type, degrading optimization.

KILLER 4: Dynamic runtime additions. Adding properties after construction instead of in the constructor creates divergent hidden classes.

// KILLER 1: Different order
function PointBad(x, y) {
  this.x = x;
  this.y = y;
}
const p1 = new PointBad(1, 2); // Map(x, y)
const p2 = new PointBad(1, 2);
p2.z = 3; // Map(x, y, z) -- different class!

// FIX: Consistent initialization
function PointGood(x, y, z) {
  this.x = x; // Always same order
  this.y = y;
  this.z = z || null; // Pre-allocate, set null if unused
}

// KILLER 2: Delete
const user = { name: 'Sai', age: 25, temp: 'xyz' };
delete user.temp; // V8 switches to dictionary mode!

// FIX: Set to null
user.temp = null; // Keeps the hidden class intact!
04Dictionary Mode: The Slow Path

When an object has too many properties added or deleted, or the hidden class transition tree gets too deep, V8 gives up. It converts the object to "Dictionary Mode" (also called "Slow Properties").

In dictionary mode, properties are stored in a hash table instead of fixed memory offsets. Property access changes from O(1) pointer arithmetic to a hash lookup — significantly slower.

This is an irreversible transition. Once an object enters dictionary mode, it never goes back to fast mode.

// Triggering dictionary mode with too many deletes
const obj = {};
for (let i = 0; i < 1000; i++) {
  obj['prop' + i] = i;
}
for (let i = 0; i < 500; i++) {
  delete obj['prop' + i]; // V8: "Too many deletes! Dict mode."
}

// Now obj is in dictionary mode. Every access is slow.
console.time('fast');
const fastObj = { a: 1, b: 2 };
for (let i = 0; i < 1000000; i++) fastObj.a;
console.timeEnd('fast');

console.time('slow');
for (let i = 0; i < 1000000; i++) obj.prop999; // Dict mode
console.timeEnd('slow'); // Much slower!
DevTools Tip: You can check if an object is in dictionary mode using Chrome DevTools. Launch Chrome with --allow-natives-syntax flag, then run %DebugPrint(obj) in the console. If you see elements: DictionaryElements, your object has been de-optimized to dictionary mode.
05Fast Properties vs Slow Properties Summary

Understanding the difference between fast and slow properties is what separates a senior JavaScript developer from a junior one.

Fast Properties: Stored in fixed memory offsets, described by a Hidden Class. O(1) access. Optimized by V8's TurboFan compiler. This is the default for well-structured objects.

Slow Properties (Dictionary Mode): Stored in a hash table. O(n) access. NOT optimized by TurboFan. Triggered by excessive property deletion or structural changes.

Object.freeze(): Tells V8 the object will never change. V8 can aggressively optimize — even inline property values in compiled code.

// Object literals are FAST (V8 knows the shape immediately)
const user = {
  name: 'Sai',   // Offset 0
  age: 25,       // Offset 1
  role: 'dev'    // Offset 2
}; // One single hidden class created upfront!

// Object.freeze optimization
const config = Object.freeze({
  host: 'localhost',
  port: 3000
});
// V8 marks this as immutable. TurboFan can inline values!

// The Hidden Class Transition Tree for user:
// Map0 (empty) -> Map1 (name) -> Map2 (name, age) -> Map3 (name, age, role)
// Every object with name, age, role in that order shares Map3.
Best Practice Summary: Construct objects completely in one go (object literals are ideal). Always initialize all properties in the constructor in the same order. Never use delete — set to null instead. Keep property types consistent. Use Object.freeze() for truly immutable objects. These habits ensure V8 keeps your objects on the fast path.

Lo kar liya — Key Points:

  • ✅ V8 uses Hidden Classes (Maps/Shapes) to describe an object's structure, storing property offsets for O(1) access
  • ✅ Adding properties in different orders creates different hidden classes, preventing V8 from optimizing
  • ✅ Inline Caches (IC) memorize property access patterns; monomorphic (1 shape) is fastest, megamorphic (5+ shapes) is slowest
  • ✅ Deleting properties forces V8 into Dictionary Mode (hash table), which is significantly slower than fixed-offset access
  • ✅ Use null instead of delete to remove a property without breaking the hidden class
  • ✅ Initialize all properties in the constructor in the same order to ensure all instances share the same hidden class
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase