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
nullinstead of adding a field later. - Avoid
deleteon hot objects; it typically drops the object into slow dictionary mode. Set the field toundefinedor build a new object instead. - Don't use one object both as a record and as a dynamic dictionary — use a
Mapfor 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)); // falseRemember
- 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
deleteon hot objects
Watch out for
- Adding optional properties conditionally after construction, creating many shapes for one 'type'.