"I don't want to memorize what V8 does. I want to understand why it exists and how it actually works."
If you've ever searched "How does Node.js work internally?", you've probably seen something like this:
- V8 parses JavaScript
- V8 produces bytecode
- V8 executes bytecode
- V8 manages the call stack
- V8 manages heap memory
- V8 performs garbage collection
Cool...
But what does any of that actually mean?
This article explains V8 from first principles, without assuming any compiler knowledge. By the end, you'll understand why each of these steps exists instead of simply memorizing them.
Before We Start: Who Actually Executes JavaScript?
Many beginners believe Node.js executes JavaScript.
It doesn't.
Think of Node.js as a restaurant owner.
V8 is the chef.
You
│
▼
Node.js
│
▼
V8 Engine
│
▼
CPU
Your JavaScript is given to the V8 Engine, and V8 is responsible for understanding and executing it.
The Real Problem
Imagine you hired a worker from another country.
You speak English.
The worker only understands Chinese.
You say:
console.log("Hello");
To you, this makes perfect sense.
To the worker, it's just strange symbols.
Exactly the same thing happens with your CPU.
The CPU doesn't understand JavaScript.
It understands only machine instructions like:
101010...
110100...
001010...
Somebody has to translate JavaScript into something the CPU understands.
That translator is V8.
Step 1 — Parsing JavaScript
Suppose we write:
let age = 25;
Open this file in Notepad.
You'll literally see:
l
e
t
a
g
e
=
2
5
;
To your computer, this is nothing more than plain text.
So the first thing V8 does is read and understand your code.
This process is called Parsing.
Think Like Your Brain
Suppose someone says:
John eats pizza.
Your brain instantly understands:
- Person → John
- Action → eats
- Object → pizza
You don't analyze individual letters.
Your brain understands the structure.
V8 does exactly the same thing.
When it sees:
let age = 25;
it understands:
Variable Declaration
Variable Name → age
Assigned Value → 25
Notice something.
Nothing has been executed.
V8 is simply understanding what the code means.
Another Example
let total = price + tax;
You instantly know what should happen:
- Read
price - Read
tax - Add them
- Store the result inside
total
V8 reaches exactly the same understanding.
Internally...
Instead of storing plain text, V8 creates a structured representation of your program called an Abstract Syntax Tree (AST).
For example:
a + b
becomes something conceptually like:
+
/ \
a b
This structure is much easier for V8 to work with than raw text.
Why Doesn't V8 Execute the Code Immediately?
Understanding the code isn't enough.
Imagine telling a robot:
Cook rice.
You understand that instruction.
The robot doesn't.
The robot needs tiny steps.
Take pot
↓
Add water
↓
Add rice
↓
Turn stove on
Computers are exactly the same.
They need extremely small instructions.
Step 2 — Generating Bytecode
After parsing, V8 converts your code into Bytecode.
Think of bytecode as a simplified language that is much easier to execute.
Your code:
let x = 5;
Conceptually becomes something like:
Create variable
↓
Store value 5
↓
Name it x
These aren't real bytecode instructions.
They're simplified just to understand the idea.
The real bytecode is more detailed.
Why Bytecode?
A common question is:
Why not generate machine code immediately?
Good question.
Imagine you're reading a 500-page book.
Would you translate the entire book into another language before reading it?
Probably not.
You'd rather translate page by page.
Generating bytecode is much faster.
It lets your application start quickly.
Step 3 — Executing the Bytecode
Now V8 begins executing those instructions.
Suppose our code is:
let x = 5;
let y = 10;
let z = x + y;
Conceptually, V8 performs something like:
Create x
↓
Store 5
↓
Create y
↓
Store 10
↓
Read x
↓
Read y
↓
Add
↓
Store result in z
One instruction at a time.
This execution is initially performed by V8's Ignition Interpreter.
Wait... Isn't Interpreting Slow?
Yes.
Imagine this loop:
for (let i = 0; i < 100000000; i++) {
}
If V8 keeps interpreting every single iteration forever, it'll become slow.
So V8 watches your code.
When it notices that a function is executed thousands of times, it says:
"This code is important."
Now another component called TurboFan takes over.
TurboFan compiles that frequently executed ("hot") code into optimized machine code.
This is called Just-In-Time (JIT) Compilation.
Instead of interpreting repeatedly:
Interpreter
↓
Interpreter
↓
Interpreter
↓
Interpreter
V8 eventually does:
Machine Code
↓
CPU
This is significantly faster.
Step 4 — The Call Stack
Let's look at functions.
function eat() {
cook();
}
function cook() {
buyVegetables();
}
function buyVegetables() {
console.log("Done");
}
eat();
How does V8 remember where it is?
It uses something called the Call Stack.
Initially:
(empty)
Call eat():
eat
Inside eat(), call cook():
cook
eat
Inside cook(), call buyVegetables():
buyVegetables
cook
eat
When buyVegetables() finishes:
cook
eat
Then:
eat
Finally:
(empty)
Notice the pattern.
The last function called is the first one to finish.
This is called LIFO (Last In, First Out).
What Happens If the Stack Never Ends?
Consider:
function hello() {
hello();
}
hello();
The stack becomes:
hello()
hello()
hello()
hello()
hello()
...
Eventually V8 runs out of stack space and throws:
RangeError:
Maximum call stack size exceeded
This is called a Stack Overflow.
Step 5 — Heap Memory
Now let's talk about objects.
Suppose we create:
let person = {
name: "Ram",
age: 25
};
Objects require memory.
Imagine the heap as a giant warehouse.
Heap
+--------------------+
| name : Ram |
| age : 25 |
+--------------------+
The variable itself doesn't contain the object.
Instead, it stores a reference (an address) pointing to where the object lives.
Conceptually:
person
↓
Object in Heap
Think of it like your phone contacts.
Your contact doesn't store your friend's house.
It stores the address of the house.
Why Do Objects Live in the Heap?
Because objects can grow.
person.city = "Kathmandu";
person.country = "Nepal";
person.phone = "9800000000";
The heap allows memory to be allocated dynamically.
Step 6 — Garbage Collection
Suppose we create:
let person = {
name: "Ram"
};
Memory looks like:
person
↓
Object
Now we write:
person = null;
The variable no longer points to the object.
person
↓
null
Object (still exists)
The object is still sitting in memory.
But nothing can reach it anymore.
Later, V8 checks memory.
It notices:
"Nobody can ever use this object again."
So it removes it and frees the memory.
This process is called Garbage Collection.
How Does V8 Know an Object Is Unused?
V8 starts from roots, such as:
- Global variables
- Variables inside currently running functions
- Closures that are still reachable
From those roots, it follows every reference.
If an object can still be reached, it stays.
If nothing points to it anymore, it becomes garbage.
A simplified view:
Global
↓
person
↓
address
↓
city
Everything connected to the roots stays alive.
But:
Old Object
(no references)
Since nothing can reach it anymore, V8 deletes it.
This is the basic idea behind the Mark-and-Sweep Garbage Collection algorithm.
Putting Everything Together
When you execute:
function greet(name) {
return `Hello ${name}`;
}
console.log(greet("Manoj"));
The journey looks like this:
JavaScript Source Code
│
▼
Parsing
│
▼
Abstract Syntax Tree (AST)
│
▼
Generate Bytecode
│
▼
Ignition Interpreter Executes Bytecode
│
▼
Manage Call Stack
│
▼
Allocate Objects in Heap
│
▼
TurboFan Optimizes Frequently Used Code
│
▼
Machine Code
│
▼
CPU Executes Instructions
│
▼
Garbage Collector Frees Unused Memory
One Important Clarification
A lot of developers think V8 is responsible for everything inside Node.js.
It isn't.
V8 is responsible for:
- Parsing JavaScript
- Creating the AST
- Generating bytecode
- Executing JavaScript
- Managing the call stack
- Managing heap memory
- Garbage collection
- Optimizing hot code with JIT compilation
However, V8 does not handle:
- File system operations (
fs) - Network I/O (
http,https,net) - Timers (
setTimeout,setInterval) - The Event Loop
- Thread Pool
These are handled by Node.js and its underlying library libuv.
A simplified architecture looks like this:
Your JavaScript
│
▼
Node.js Runtime
┌─────────────────────────┐
│ │
▼ ▼
V8 Engine libuv
(JavaScript Execution) (I/O & Event Loop)
│ │
└──────────────┬──────────┘
▼
Operating System
▼
CPU
Understanding this separation is one of the biggest milestones in learning how Node.js actually works.
Final Thoughts
Don't waste energy memorizing terms like AST, bytecode, and heap allocations.
The core concept is simple: JavaScript is just plain text, and your CPU only handles binary. Everything V8 does, from parsing and interpreting to optimizing and cleaning up memory, exists purely to bridge that specific gap. Once you view the engine as a practical translator rather than a black box of magic, the architecture makes perfect sense.
Top comments (0)