How Hidden Classes Optimize JavaScript in Chromium

This article explores how the Chromium web browser, specifically through its V8 JavaScript engine, optimizes property access on dynamic JavaScript objects using hidden classes, internally known as Maps. Because JavaScript is a dynamically typed language, objects can change structure at runtime, which traditionally forces engines to use slow dictionary-like lookups to find properties. You will learn how V8 overcomes this limitation by assigning virtual shapes to objects, creating transition chains, and utilizing inline caching to achieve property access speeds comparable to statically typed languages.

The Challenge of Dynamic Objects

In statically typed languages like C++, object structures are fixed at compile time. Because the layout of an object never changes, the compiler knows the exact memory offset of every property. Accessing a property is as simple as reading a specific memory address.

JavaScript is dynamically typed, allowing developers to add or delete properties on the fly. Without optimization, a JavaScript engine must treat objects like hash tables (dictionaries). Every time a property is accessed, the engine has to compute a hash of the property name to find its value in memory. This process is computationally expensive and significantly slows down execution.

What Are Hidden Classes (Maps)?

To avoid slow dictionary lookups, Chromium’s V8 engine attaches a hidden class—referred to as a "Map" in V8's source code—to every JavaScript object.

A hidden class separates an object's structure (its shape) from its data (its values). Instead of storing property names and values together in a dictionary, V8 stores the object's values in a flat, contiguous array in memory. The hidden class acts as a blueprint, storing the property names and their corresponding index (memory offset) within that value array.

When a property is accessed, V8 consults the object's hidden class, retrieves the exact memory offset, and jumps straight to the value in the array.

Transition Chains and Shared Shapes

Because JavaScript objects mutate, hidden classes must adapt dynamically. V8 handles this by creating "transition chains" as properties are added to an object:

  1. Initial State: When you create an empty object (const obj = {}), V8 assigns it an initial empty hidden class, let's call it C0.
  2. First Property: When you add a property (obj.x = 10), V8 creates a new hidden class, C1, which inherits from C0. C1 records that property x is located at offset 0. V8 then updates the object to point to C1.
  3. Transition Link: Crucially, V8 adds a "transition" pointer on C0 that says: "If property x is added to an object with class C0, transition it to class C1."
  4. Second Property: Adding another property (obj.y = 20) creates hidden class C2, recording y at offset 1, and links C1 to C2.

This transition mechanism allows objects to share hidden classes. If you create a second empty object and add x and y in the exact same order, V8 will not create new hidden classes. Instead, it follows the existing transition chain from C0 to C1 to C2. Both objects now share the C2 hidden class, saving memory and establishing that both objects have the exact same structural layout.

The Payoff: Inline Caching

Hidden classes unlock V8's most powerful optimization technique: Inline Caching (IC).

When a function accesses an object's property, V8 observes the hidden class of the object passed into that function. For example, consider a function that returns obj.y.

  1. First Execution: The first time the function runs, V8 looks up the object's hidden class (C2), finds that y is at offset 1, and retrieves the value.
  2. Caching: V8 then modifies the compiled machine code of the function itself—a process called inline caching. It hardcodes the expected hidden class (C2) and the offset (1) directly into the instruction path.
  3. Subsequent Executions: The next time the function is called, V8 does not look up the property name at all. It simply checks if the incoming object has the hidden class C2. If it matches, V8 immediately reads the memory at offset 1.

By combining hidden classes with inline caching, Chromium reduces a complex dynamic property lookup into a single comparison and a direct memory read.

Deoptimization and Property Order

The reliance on transition chains means that the order in which properties are added matters.

If Object A is assigned x then y, and Object B is assigned y then x, they will follow different transition paths and end up with two different hidden classes. Furthermore, the memory offsets for x and y will be reversed between the two objects.

If both objects are passed into the same function, the inline cache will fail because the hidden classes do not match. If a function receives objects with too many different hidden classes, V8 gives up on inline caching for that operation (a state known as megamorphism) and falls back to slower lookup methods. Maintaining consistent object initialization orders ensures Chromium can reuse hidden classes and keep property access fully optimized.