Browser Engine Internals

How V8 turns your JavaScript into machine code at incredible speed.

JS Engines

The programs that execute your JavaScript code.

EngineUsed byCreated by
V8Chrome, Edge, Node.js, DenoGoogle
SpiderMonkeyFirefoxMozilla
JavaScriptCoreSafari, BunApple
// A JS engine's job:
// 1. Parse your source code into an AST
// 2. Compile it (bytecode → machine code)
// 3. Execute the machine code
// 4. Optimize hot paths (JIT)
// 5. Manage memory (garbage collection)

// The engine is ONE part of the browser:
// Browser = Engine + Rendering + Networking + Storage + ...
//
// V8 Pipeline (Chrome/Node):
// Source → Parser → AST → Ignition (bytecode)
//                         → TurboFan (optimized machine code)
//
// Hot code gets compiled to fast machine code.
// Assumptions wrong? Deoptimize back to bytecode.

Parsing

Turning source code into a tree the engine can work with.

// Step 1: Tokenization (Lexer)
// Source: "const x = 5 + 3;"
// Tokens: [const] [x] [=] [5] [+] [3] [;]

// Step 2: Parse into Abstract Syntax Tree (AST):
// VariableDeclaration
// ├─ kind: "const"
// └─ declarations:
//    └─ VariableDeclarator
//       ├─ id: Identifier("x")
//       └─ init: BinaryExpression
//              ├─ operator: "+"
//              ├─ left: Literal(5)
//              └─ right: Literal(3)

// You can see ASTs at: astexplorer.net

The parser converts source text into a structured tree. Each language construct becomes a node in the AST.

1 / 2

JIT Compilation

Just-In-Time: compile hot code to machine code on the fly.

// V8's two-tier compilation:
// 
// Tier 1: Ignition (interpreter)
// - Compiles to bytecode quickly
// - Slower execution, but fast startup
// - Collects type feedback (profiling)
//
// Tier 2: TurboFan (optimizing compiler)
// - Compiles hot functions to machine code
// - Much faster execution
// - Uses type feedback for aggressive optimization
// - Can "deoptimize" if assumptions break

// Example of optimization:
function add(a, b) {
  return a + b;
}

// Called 1000x with numbers:
for (let i = 0; i < 1000; i++) add(i, i);
// V8 thinks: "add always gets numbers → optimize for numbers"
// TurboFan generates fast machine code for number addition

// Then:
add("hello", " world"); // string! Assumption broken!
// V8: "deoptimize! Back to bytecode interpreter"
// Has to re-learn the types

Write optimization-friendly code:

  • • Use consistent types (don't mix numbers and strings)
  • • Initialize objects with all properties upfront
  • • Avoid deleting properties (use undefined instead)
  • • Use typed arrays for number-heavy code

Hidden Classes

How V8 makes property access fast on dynamic objects.

// JavaScript objects are dynamic — you can add properties anytime.
// But V8 creates "hidden classes" (Shapes/Maps) for structure.

// Objects with the same shape share a hidden class:
const a = { x: 1, y: 2 };
const b = { x: 5, y: 10 };
// Same hidden class! → fast property access

// Different property order = different hidden class:
const c = { y: 2, x: 1 }; // different class than a!

// Adding properties changes the hidden class:
const point = {};     // Hidden Class 0 (empty)
point.x = 1;         // Hidden Class 1 (has x)
point.y = 2;         // Hidden Class 2 (has x, y)

// ✓ Better: consistent initialization
function Point(x, y) {
  this.x = x;  // always same order
  this.y = y;  // always same properties
}
const p1 = new Point(1, 2); // same hidden class
const p2 = new Point(3, 4); // same hidden class → optimized!
// Inline Caching (IC): remembering where properties are
// 
// First access of obj.x:
//   Engine looks up hidden class, finds x at offset 0
//   Caches: "for this hidden class, x is at offset 0"
//
// Next access of obj.x (same hidden class):
//   Direct memory read at offset 0 — no lookup!
//   Nearly as fast as C struct access

// This breaks if objects have different shapes:
function getX(obj) { return obj.x; }

getX({ x: 1 });           // monomorphic (one shape) → fast
getX({ x: 2, y: 3 });     // polymorphic (two shapes) → slower
getX({ x: 1, a: 2, b: 3 }); // megamorphic (many shapes) → slow

// Lesson: pass objects with consistent shapes to functions

Garbage Collection

Automatic memory management — freeing objects you no longer need.

// V8 uses "generational" garbage collection:
//
// Young Generation (Scavenger):
// - Small, fast area for new objects
// - Most objects die young (temporary values)
// - Collected frequently (~every few ms)
// - Uses "semi-space" copy algorithm
//
// Old Generation (Major GC):
// - Large area for long-lived objects
// - Objects that survive multiple young GCs
// - Collected less often
// - Uses "mark-sweep-compact" algorithm

// The GC process:
// 1. Mark: find all reachable objects from roots
//    (global, stack variables, closures)
// 2. Sweep: free memory of unmarked objects
// 3. Compact: defragment (move objects together)

V8 splits memory into two generations. Most objects die quickly (young gen), so collecting them is fast. Long-lived objects are promoted to old gen.

1 / 2

FAQ

Common questions about JS engine internals.