2026-04-22

How the V8 Engine Actually Runs Your JavaScript

A visual guide to V8 internals. Parsing, JIT compilation, hidden classes, garbage collection, all explained simply with working examples.

javascriptv8internalsperformance

Okay, so most of us just write JavaScript and hope it runs fast. You write a function, it works, done. But sometimes things are slow and you have no idea why. Or you read something like “avoid delete on objects” and you just blindly follow it without knowing what’s actually happening inside.

So let me explain what V8 is actually doing with your code. I’ll keep it simple and use the interactive demos below to make it visual. By the end you’ll understand why some code is fast and some is not, and it will actually make sense.

1. First, V8 has to read your code

Think of it like this. Imagine you give someone a recipe written in Urdu but they only understand ingredient lists and cooking steps in a specific format. Before they can cook anything, they need to first read and understand your recipe.

V8 does the same thing. Your JavaScript is just text. V8 first sends it through a scanner which breaks it into pieces. So this:

function add(a, b)

becomes something like this:

FUNCTION  IDENTIFIER("add")  LPAREN  IDENTIFIER("a")  COMMA  IDENTIFIER("b")  RPAREN

Then the parser takes all those tokens and builds a tree structure called an AST (Abstract Syntax Tree). Think of it like an org chart of your code. Every part of your program becomes a node in this tree.

Abstract Syntax Tree

function add(a, b) { return a + b; }

ProgramFunctionDeclarationIdentifier"add"Params[a, b]BlockStatementReturnStatementBinaryExpr( + )Identifier"a"Identifier"b"

Each node is a syntactic construct. The parser builds this tree before any code runs.

This tree is what V8 actually works with from here on. Nothing has run yet, it’s just understanding what you wrote.

One smart thing V8 does here is called lazy parsing. If you have a big file with lots of functions, V8 doesn’t fully parse every function immediately. It only does a quick scan to find where each function starts and ends. It fully parses a function only when you actually call it. This is why big JavaScript bundles still slow down page load even when most of that code is never used. The initial scanning still takes time.

2. How it actually executes your code

Okay so now V8 has the tree. What does it do with it? This is where it gets really interesting.

V8 compilation pipeline

1 / 9

function add (a, b) {
  return a + b;
}

Source

JS text

Scanner

Tokenizer

Parser

Syntax analysis

AST

Syntax tree

Ignition

Interpreter

Bytecode

Low-level ops

profiler is watching…

V8 receives your JavaScript as plain text.

Step through the pipeline above. Each step shows what V8 is doing at that stage.

V8 has two main engines inside it. The first one is called Ignition. It takes your AST and converts it into something called bytecode. Bytecode is simpler than JavaScript but not quite machine code yet. Think of it like an intermediate language. Ignition then runs this bytecode directly.

For most of your code this is perfectly fine. Ignition is fast enough. But here is the thing, Ignition is also watching. It is counting how many times each function gets called, what types of values are being passed in, all of that.

When a function gets called a lot (like thousands of times in a loop), Ignition says okay this one is important and sends it to the second engine called TurboFan.

TurboFan is V8’s optimizing compiler. It looks at all the data Ignition collected and generates super optimized machine code specifically for your CPU. Like if it noticed that your function always gets numbers, it will compile a version that treats those values as plain numbers and skips all the JavaScript type checking overhead. This can make code run 10x to 100x faster.

But here is the catch. If TurboFan assumed your function always gets numbers, and then you pass it a string, it panics. It has to throw away all that optimized code and go back to Ignition. This is called deoptimization and it is expensive. So the lesson here is to be consistent with your types. Don’t pass a number sometimes and a string other times to the same function.

3. The hidden classes thing that nobody explains properly

This one confuses people a lot. Let me explain it with an analogy.

Imagine you have a form with fixed fields. Name, age, city. Because the fields are always in the same order, you can just say “field number 2 is age” and directly jump to it. Super fast.

Now imagine everyone fills the form differently. One person puts age first, another puts city first. Now you can’t just jump to “field number 2” because it’s different for every form. You have to search through each form to find the age field. Much slower.

V8 does exactly this with your objects. It creates internal things called hidden classes (some people call them shapes) that track what properties an object has and where in memory each property lives. If two objects have the same properties added in the same order, they share the same hidden class. V8 can then look up properties by memory offset directly, no searching needed.

Hidden classes & shapes

step 1 of 6

✓ fast consistent property order

const p1 = {};

C0

∅ empty

✗ slow different property order

const p2 = {};

// coming up…

C0

∅ empty

Step through both examples. When you add properties in the same order every time, objects share hidden classes and lookups are instant. When you add them in different orders, V8 has to create separate hidden classes for each variation and nothing gets shared.

So practically what does this mean for you:

Don’t do this:

// sometimes you add x first, sometimes y first
if (condition) {
  obj.x = 1;
  obj.y = 2;
} else {
  obj.y = 2;
  obj.x = 1;
}

Just always add properties in the same order. Also avoid using delete on object properties because that also messes up the hidden class. And if you are creating lots of similar objects, a class or constructor function is better than random object literals because it guarantees the same shape every time.

4. Garbage collection and memory

JavaScript handles memory for you automatically which is nice. But it’s good to know how it works because if you write code that creates too much garbage, your app will stutter.

The analogy I like is a hostel common room. Young people come in, hang out for a bit, and leave. Some people end up staying long term and become permanent residents. The cleaners (GC) mostly focus on the common areas where turnover is high because that is where most of the mess happens.

V8’s garbage collector works exactly like this. It divides memory into two areas.

Generational garbage collector

GC round: 0  ·  young: 0 objects  ·  old: 0 objects

Young generation (Nursery)

Minor GC, runs frequently

no young objects

Old generation (Tenured)

Major GC, runs rarely

no promoted objects yet

new = just allocated +1 +2 = survived GC rounds T2+ = promoted to old gen faded = unreachable, being collected

Click alloc a few times to create objects, then run the GC. Watch what happens. New objects go into the young generation. The GC sweeps this area very frequently but it’s a small area so it’s fast. Objects that survive a few rounds of GC get promoted to the old generation because V8 figures they’re going to stick around.

The old generation gets cleaned much less often because it’s bigger and takes longer. V8 also does this cleaning in the background so it doesn’t freeze your page.

So what does this mean for you practically? If you are creating thousands of temporary objects in a hot loop, you are creating a lot of garbage in the young generation and the GC has to keep cleaning it up. Better to reuse objects where possible. Also watch out for closures that accidentally hold on to big chunks of data. Those objects never get collected and slowly eat your memory.

So to wrap it up

V8 reads your code and builds a tree, converts it to bytecode, watches which functions are hot, compiles those to native machine code, tracks your object shapes to do fast property lookups, and manages memory in generations so cleanup is efficient.

You don’t need to think about all of this all the time. Most of the time V8 is fast and it doesn’t matter. But when your app is slow and you are trying to figure out why, this is what’s happening under the hood. And honestly once you understand it, a lot of the “best practices” you’ve been following start to actually make sense instead of just being rules you memorized.

Related sprint

Need this turned into a product?

Ship a narrow SaaS MVP with auth, database, payments, deployment, source code, and handoff in one focused sprint.

See MVP development sprint

Need this shipped?

Reading is useful. A focused sprint turns the idea into working software.