V8 Engine Internals: How JavaScript Gets Compiled
Have you ever wondered how JavaScript runs so fast in modern browsers? 🚀
V8 Engine Internals: How JavaScript Gets Compiled
Have you ever wondered how JavaScript runs so fast in modern browsers? 🚀
When I write JavaScript code in Chrome or run it with Node.js, it executes incredibly fast — almost instantly! But here’s what’s interesting: JavaScript is traditionally an interpreted language.
Wait, what does “interpreted” mean? 🤔
Let me explain with a simple analogy:
Imagine you’re reading a recipe in a foreign language. You have two options:
Option 1: Interpreter (Line by line translation)
- You read the first line
- Translate it to your language
- Follow that instruction
- Move to the next line
- Translate it
- Follow it… and so on
Option 2: Compiler (Translate everything first)
- Translate the entire recipe to your language first
- Then follow all the instructions quickly without translating
Interpreted languages (like JavaScript, Python) work like Option 1 — they read and execute code line by line, translating as they go.
Compiled languages (like C++, Java) work like Option 2 — they translate the entire code to machine language first, then run it.
The problem? Reading and translating line by line (interpreting) is slower than having everything pre-translated (compiled).
So JavaScript should be slower, right? But somehow Chrome and Node.js make it blazingly fast! what’s the secret behind this speed?
The answer lies in The V8 Engine — a powerful JavaScript engine built by Google that completely changed how JavaScript runs.
So in this article, I’ll break down V8 step by step — with practical examples and real-world insights..
Let’s dive in! 🏊♂️
First, What is V8?
V8 is Google’s JavaScript engine. Think of it as the brain that runs your JavaScript code. It’s written in C++ and it powers:
- Google Chrome (that’s why Chrome is so fast!)
- Node.js (yes, the same engine!)
- Microsoft Edge (switched to V8 in 2020)
But here’s what makes V8 special: it doesn’t just interpret JavaScript line by line.
Traditional interpreters read your code, understand it, and execute it — like reading a recipe and cooking as you go. That’s… okay, but not super fast.
V8 does something clever: it compiles JavaScript into machine code that your computer’s processor can run directly. That’s why it’s very fast!
Let Me Explain With a Real-Life Example
Imagine you’re opening a restaurant. You have recipes (your JavaScript code), and you need to cook the food for customers.
Option 1: Interpreter
- A cook reads the recipe line by line
- Cooks one step at a time
- Every single order is made this way
- It works, but if someone orders the same dish 100 times? You’re reading that recipe 100 times!
Option 2: The V8
- First order comes in → the cook reads the recipe and makes the dish (interpreter)
- But wait! This dish is popular
- The head chef memorizes the recipe and creates an optimized, super-fast process (compiler)
- Now when orders come in, Food is ready in seconds
That’s exactly what V8 does! It starts by interpreting (quick start), but then compiles popular code into optimized machine code (lightning fast execution). Let see thejourney.
The Journey: How Your Code Actually Runs
Okay, when you write JavaScript and run it, what actually happens? Let me walk you through the journey:
Your JavaScript Code
↓
🔍 Parsing
↓
📋 Abstract Syntax Tree (AST)
↓
⚡ Ignition (Interpreter)
↓
📊 Profiler (watching hot code)
↓
🚀 TurboFan (Compiler)
↓
💻 Optimized Machine Code
Let’s say you wrote this simple function:
function add(a, b) {
return a + b;
}
const result = add(5, 10);
Simple, right? But behind the scenes, V8 is doing some amazing things with this code.
Step 1: Parsing — V8 Reads Your Code
First, V8 needs to understand what you wrote. This is called parsing.
Think of it like this: when you read a sentence, your brain breaks it into words, identifies verbs and nouns, and understands the meaning. V8 does the same with your code!
What happens:
V8 breaks your code into tiny pieces called tokens:
function→ Oh, this is a function keywordadd→ This is the function name(→ Opening parenthesisa→ First parameter,→ Commab→ Second parameter- And so on…
Then it builds an Abstract Syntax Tree (AST) — basically a tree structure that represents your code’s logic:
FunctionDeclaration: add
├── Parameters: [a, b]
└── Body:
└── Return
└── Add operation
├── Left: a
└── Right: b
Why does this matter?
Well, the AST is like a blueprint. It helps V8 understand: “Okay, this function takes two parameters and adds them together.”
Also, this is where V8 catches syntax errors! If you forgot a closing bracket, the parser will yell at you here.
Step 2: Ignition — Let’s Start Running!
After parsing, V8 hands the AST to something called Ignition — V8’s interpreter.
Now you might have question: “Wait, I thought V8 was a compiler? What’s this interpreter doing here?”
Great question! Here’s the answer: compiling code takes time and memory. If V8 compiled everything immediately, your app would take forever to start!
So V8 uses a smart strategy:
- Ignition (interpreter) runs your code quickly right away
- Meanwhile, it watches for code that runs a lot
- Then TurboFan (compiler) optimizes that hot code
It’s like having a quick cook for instant orders, and a master chef who perfects the popular dishes!
What Ignition does:
It converts the AST into bytecode — a kind of middle ground between JavaScript and machine code.
For our add function:
Ldar a1 // Load parameter 'a'
Add a0 // Add parameter 'b'
Return // Return the result
This bytecode runs immediately. Your code is now executing!
But V8 isn’t done yet…
Step 3: Profiling — V8 Is Watching
While Ignition runs your code, V8’s profiler is working in the background like a detective.
It’s asking questions:
- “How many times is this function called?”
- “What types of data are being used? Numbers? Strings? Objects?”
- “Are there any patterns here?”
Let’s say you have this code:
function calculateTotal(items) {
let total = 0;
for (let i = 0; i < items.length; i++) {
total += items[i].price;
}
return total;
}
// This function gets called 10,000 times!
for (let i = 0; i < 10000; i++) {
calculateTotal(shoppingCart);
}
The profiler notices: “Whoa! This calculateTotal function is running 10,000 times! And it's always receiving an array of objects with a 'price' property. This is HOT CODE!"
When code becomes “hot” (runs frequently), V8 makes a decision: “Let’s optimize this!”
That’s when TurboFan enters the scene.
Step 4: TurboFan — Maximum Optimization!
TurboFan is V8’s optimizing compiler — the master chef who perfects the recipe.
It takes that hot code and generates highly optimized machine code that runs directly on your CPU. No interpretation, no bytecode — just raw, blazing-fast machine instructions.
But how does it optimize? Let me show you some of its clever tricks:
Trick 1: Inline Caching
Let’s say you’re accessing object properties:
function getPrice(product) {
return product.price;
}
const item1 = { name: "Laptop", price: 1000 };
const item2 = { name: "Mouse", price: 25 };
getPrice(item1); // First call
getPrice(item2); // Second call
Normally, JavaScript has to search for the price property each time. But V8 notices: "Hey, both objects have the same structure! I can remember where 'price' is located!"
So it caches the property location. Next time? Direct access. Super fast!
Trick 2: Hidden Classes
Okay, this one might sound mysterious, but it’s actually really cool! Let me explain what “hidden classes” are.
What are Hidden Classes?
You know how in JavaScript, objects can have any properties you want? You can create objects on the fly:
const user = {};
user.name = "Saravana";
user.age = 25;
user.city = "Chennai";
This flexibility is great for us developers, but it creates a problem for V8: How does it know where to find these properties in memory?
In languages like C++ or Java, the compiler knows exactly where each property is stored because classes are defined upfront. But JavaScript objects are dynamic!
So V8 invented hidden classes (also called “shapes” or “maps”) — it’s an internal structure that V8 creates behind the scenes to track the “shape” of your objects.
Think of it like this: When you create objects with the same properties in the same order, V8 says “okay! These objects have the same blueprint!” and it assigns them the same hidden class.
// These two objects have the SAME hidden class
const obj1 = { x: 1, y: 2 };
const obj2 = { x: 5, y: 10 };
// V8 thinks: "Both have properties 'x' and 'y' in that order. Same shape!"
// But this one has a DIFFERENT hidden class!
const obj3 = { y: 2, x: 1 }; // Properties in different order
// V8 thinks: "Wait, this one has 'y' first, then 'x'. Different shape!"
Wait, why does property order matter?
Because V8 uses the hidden class to figure out where properties are stored in memory. Same hidden class = same memory layout = super fast access!
Here’s a real example of how this affects you:
function Point(x, y) {
this.x = x;
this.y = y;
}
const p1 = new Point(1, 2); // V8 creates Hidden Class A
const p2 = new Point(5, 10); // Same structure! Uses Hidden Class A - FAST!
// But then you do this:
p1.z = 3; // Added a new property!
// V8 creates a NEW Hidden Class B for p1 - SLOWER!
What happened? When you added the z property to p1, its shape changed! Now p1 and p2 have different hidden classes, so V8 can't use the same optimized code for both.
Why does V8 care so much about hidden classes?
When objects share the same hidden class, V8 can:
- Reuse optimized machine code
- Know exactly where properties are in memory
- Make property access super fast
When objects have different hidden classes, V8 has to:
- Generate different code for each shape
- Do slower property lookups
- Work much harder
I’ll show you how to keep your objects using the same hidden classes (and make V8 happy!) in a bit!
wait? I have a thought: “Okay, so how do I write code that V8 loves?”
Great question! Let me show you.
Writing V8-Friendly Code 💡
Now that you understand how V8 works, you can write faster JavaScript! Here’s what I learned:
- Keep Your Types Consistent
// ❌ Bad - Type changes cause deoptimization
function calculate(value) {
return value * 2;
}
calculate(5); // number
calculate("10"); // string - BAD!
// ✅ Good - Always use the same type
function calculate(value) {
if (typeof value !== 'number') {
value = Number(value); // Convert once, upfront
}
return value * 2;
}
2. Initialize Objects the Same Way
// ❌ Bad - Different property orders
const user1 = { name: "Saravana", age: 25 };
const user2 = { age: 30, name: "John" }; // Different order!
// ✅ Good - Same property order
const user1 = { name: "Saravana", age: 25 };
const user2 = { name: "John", age: 30 };
3. Don’t Add Properties Later
// ❌ Bad - Adding properties dynamically
function createPoint(x, y) {
const point = { x: x, y: y };
return point;
}
const p = createPoint(1, 2);
p.z = 3; // Added later - changes hidden class!
// ✅ Good - Initialize all properties upfront
function createPoint(x, y, z) {
return {
x: x,
y: y,
z: z || 0 // Initialize even if optional
};
}
4. Use Constructor Functions Consistently
// ✅ Good pattern
function Point(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
const p1 = new Point(1, 2, 3);
const p2 = new Point(4, 5, 6);
// Same hidden class! V8 is happy!
5. Avoid Try-Catch in Hot Code
// ❌ Bad - try-catch blocks prevent optimization
function hotFunction(x) {
try {
return x * 2;
} catch (e) {
return 0;
}
}
// ✅ Good - Move try-catch outside hot paths
function safeCalculate(x) {
return calculate(x);
}
function calculate(x) {
return x * 2; // This can be optimized!
}
A Real Example: Before & After
Let me show you how these principles work in practice.
❌ Before (Slow):
function processUsers(users) {
let result = [];
for (let i = 0; i < users.length; i++) {
let user = users[i];
// Type inconsistency
if (typeof user.age === 'string') {
user.age = parseInt(user.age);
}
// Adding properties dynamically
user.processed = true;
user.timestamp = Date.now();
result.push(user);
}
return result;
}
Problems:
- Type checking in every loop iteration
- Adding properties dynamically (changes hidden class)
- Not using optimized array methods
✅ After (Fast):
function processUsers(users) {
return users.map(user => ({
...user,
age: typeof user.age === 'string' ? parseInt(user.age) : user.age,
processed: true,
timestamp: Date.now()
}));
}
Why is this better?
- Type normalization happens once per user
- All properties initialized together (consistent hidden class)
- Uses native
mapmethod (highly optimized by V8) - V8 can inline and optimize aggressively!
Conclusion:
I hope this deep dive into V8 was helpful and not too overwhelming. If you have questions or found something confusing, let me know — I’m always happy to clarify!
Suggestions and criticisms are highly appreciated ❤️
Want to learn more?
- V8 Official Blog: https://v8.dev/blog
- V8 Documentation: https://v8.dev/docs
- Understanding V8’s TurboFan: https://v8.dev/blog/turbofan-jit
메타데이터
- post_id
- e2716636c82f
- slug
- v8-engine-internals-how-javascript-gets-compiled-e2716636c82f
- url
- https://medium.com/@saravanaeswari22/v8-engine-internals-how-javascript-gets-compiled-e2716636c82f
- canonical_url
- https://medium.com/@saravanaeswari22/v8-engine-internals-how-javascript-gets-compiled-e2716636c82f
- author_url
- https://medium.com/@saravanaeswari22
- status
- ok
- fetched_at
- 2026-07-07 11:15:25