Architecture

Understanding V8 Internals: Hidden Classes, Inline Caches, and JIT Compiler

Master Javascript compilation internals. Learn how V8 compiles code dynamically using Ignition, Turbofan, Inline Caches, and Hidden Classes.

Sachin Sharma
Sachin SharmaCreator
May 31, 2026
5 min read
Understanding V8 Internals: Hidden Classes, Inline Caches, and JIT Compiler
Featured Resource
Quick Overview

Master Javascript compilation internals. Learn how V8 compiles code dynamically using Ignition, Turbofan, Inline Caches, and Hidden Classes.

Understanding V8 Internals: Hidden Classes, Inline Caches, and JIT Compiler

JavaScript is an incredibly flexible, dynamic language. We can instantiate an empty object, dynamically attach random properties to it at runtime, delete keys on the fly, and change variable types from numbers to strings without ever compiling our code manually.

To the user, this dynamic flexibility is highly convenient. To a virtual machine executing code at high speed, however, it is a complete nightmare.

In statically typed languages (like C++ or Rust), property access offsets are determined at compile time. The compiled machine instruction knows exactly where in memory a variable is situated relative to the object's pointer offset (e.g., "read 4 bytes from pointer offset X"). In dynamic JavaScript, a property access like user.name usually requires a heavy hash-map dictionary lookup on every execution.

How does the Google Chrome V8 engine run this highly dynamic, uncompiled code at near-native speeds?

In this deep-dive article, we will explore V8 compiler internals, uncovering the mechanics of Hidden Classes, Inline Caches (ICs), and the Ignition/TurboFan JIT compilation architecture.


⚡ 1. The V8 JIT Compilation Pipeline: From Source to Machine Code

V8 does not interpret JavaScript line-by-line like early interpreters. Instead, it utilizes a Just-In-Time (JIT) Compiler pipeline:

[JS Source Code] ──> [Parser (AST)] ──> [Ignition (Bytecode Interpreter)]
                                                    │ (Collects Profiling Telemetry)
[Machine Code] <── [TurboFan JIT Compiler] <────────┘ (Optimizes "Hot" Code Paths)
  1. 2.
    Parsing: The engine parses raw text strings into an Abstract Syntax Tree (AST).
  2. 4.
    Ignition Interpreter: V8's bytecode interpreter, Ignition, reads the AST and generates lightweight bytecode. As it executes this bytecode, Ignition acts as a profiler—it monitors how often functions run ("hotness") and collects metadata about variable types.
  3. 6.
    TurboFan Compiler: If a function becomes extremely "hot" (executed frequently), V8 pushes the bytecode to TurboFan, a highly advanced JIT optimizing compiler. TurboFan reads Ignition's telemetry, assumes the variable types will remain consistent, and compiles the bytecode into highly optimized native machine code for the CPU.
  4. 8.
    Deoptimization: If a variable type changes unexpectedly (e.g., passing a string into a function that previously only received integers), TurboFan's assumptions fail, and it executes a costly deoptimization step, dropping back down to interpreted bytecode.

🏗️ 2. The Magic of Hidden Classes (Shapes)

Because JS objects don't have compile-time type definitions, V8 programmatically creates Hidden Classes (also called Shapes or Maps) under the hood.

Every object holds a internal pointer to a Hidden Class. This class tracks the memory offset locations of all properties attached to the object.

Let’s analyze how V8 dynamically compiles objects step-by-step:

javascript
const user = {}; user.x = 5; user.y = 10;
  1. 2.
    Initial State: V8 instantiates an empty object user. It attaches a default hidden class to it: C0.
  2. 4.
    Adding Property x: When you execute user.x = 5, V8 registers that a property has been added. It creates a new hidden class C1 (which defines x at memory offset 0) and establishes a transition path from C0 to C1. The user object's internal shape pointer is updated to C1.
  3. 6.
    Adding Property y: When you execute user.y = 10, V8 transitions to another new hidden class C2 (which defines x at offset 0, and y at offset 1) and creates a transition path from C1 to C2.

Why Shape Consistency Matters

If you instantiate another object in the exact same sequence:

javascript
const admin = {}; admin.x = 20; admin.y = 40;

V8 recognizes that admin matches the transition paths of user. It immediately reuses hidden classes C0, C1, and C2. Both objects now share the identical Hidden Class C2, enabling highly optimized, compile-time property offsets!

However, if you write:

javascript
const guest = {}; guest.y = 30; // Different property sequence! guest.x = 15;

V8 is forced to create a completely distinct branch of Hidden Classes because the transition order changed. The objects now have different shapes, fracturing V8's ability to cache offset references.


🛠️ 3. Inline Caches (ICs): Eliminating Lookup Overhead

Once V8 has established hidden class shapes, it optimizes property reads using Inline Caches (ICs).

When a function executes a property access like user.x, V8 intercepts the operation. The first time it runs, it performs a costly search inside the Hidden Class to locate x.

However, V8 caches the memory offset directly inside the compiled call site! On subsequent executions of that function, the compiled code bypasses the property lookup completely. It simply executes:

  1. 2.
    Check if the object's hidden class pointer still matches the cached class (e.g., C2).
  2. 4.
    If yes, instantly read memory from the cached offset (e.g., offset 0).

This simple check-and-read operation is incredibly fast, performing at near C++ class property compilation speeds.


🚀 4. How to Write V8-Optimized JavaScript

Understanding these compiler internals allows you to write JavaScript that runs significantly faster:

  1. 2.
    Initialize all properties in the constructor: Never add properties dynamically after instantiating an object. Always define all fields upfront so objects share a single, stable hidden class.
  2. 4.
    Maintain parameter order: Ensure you assign properties in the exact same sequence across all files.
  3. 6.
    Avoid delete operations: Using delete user.x throws the object out of V8's highly optimized hidden class structures back into a slow, hash-table dictionary fallback. Always set fields to null or undefined instead of deleting them.

🏁 5. Conclusion: Mechanical Sympathy

Writing high-performance JavaScript requires mechanical sympathy—an understanding of the underlying compilation machinery. By designing your data structures to maintain consistent hidden shapes, avoiding dynamic property extensions, and keeping JIT compiler optimizations stable, you allow V8's JIT compilers to run your web applications at native CPU performance.

Sachin Sharma

Sachin Sharma

Software Developer

Building digital experiences at the intersection of design and code. Sharing weekly insights on engineering, productivity, and the future of tech.