DEV Community

Manoj Khatri
Manoj Khatri

Posted on

Understanding the V8 Engine (The Brain Behind Node.js) — From First Principles

"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

Enter fullscreen mode Exit fullscreen mode

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");

Enter fullscreen mode Exit fullscreen mode

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...

Enter fullscreen mode Exit fullscreen mode

Somebody has to translate JavaScript into something the CPU understands.

That translator is V8.


Step 1 — Parsing JavaScript

Suppose we write:

let age = 25;

Enter fullscreen mode Exit fullscreen mode

Open this file in Notepad.

You'll literally see:

l
e
t

a
g
e

=

2
5
;

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

it understands:

Variable Declaration

Variable Name → age

Assigned Value → 25

Enter fullscreen mode Exit fullscreen mode

Notice something.

Nothing has been executed.

V8 is simply understanding what the code means.


Another Example

let total = price + tax;

Enter fullscreen mode Exit fullscreen mode

You instantly know what should happen:

  1. Read price
  2. Read tax
  3. Add them
  4. 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

Enter fullscreen mode Exit fullscreen mode

becomes something conceptually like:

     +
    / \
   a   b

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

Conceptually becomes something like:

Create variable

↓

Store value 5

↓

Name it x

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

Conceptually, V8 performs something like:

Create x

↓

Store 5

↓

Create y

↓

Store 10

↓

Read x

↓

Read y

↓

Add

↓

Store result in z

Enter fullscreen mode Exit fullscreen mode

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++) {

}

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

V8 eventually does:

Machine Code

↓

CPU

Enter fullscreen mode Exit fullscreen mode

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();

Enter fullscreen mode Exit fullscreen mode

How does V8 remember where it is?

It uses something called the Call Stack.

Initially:

(empty)

Enter fullscreen mode Exit fullscreen mode

Call eat():

eat

Enter fullscreen mode Exit fullscreen mode

Inside eat(), call cook():

cook

eat

Enter fullscreen mode Exit fullscreen mode

Inside cook(), call buyVegetables():

buyVegetables

cook

eat

Enter fullscreen mode Exit fullscreen mode

When buyVegetables() finishes:

cook

eat

Enter fullscreen mode Exit fullscreen mode

Then:

eat

Enter fullscreen mode Exit fullscreen mode

Finally:

(empty)

Enter fullscreen mode Exit fullscreen mode

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();

Enter fullscreen mode Exit fullscreen mode

The stack becomes:

hello()

hello()

hello()

hello()

hello()

...

Enter fullscreen mode Exit fullscreen mode

Eventually V8 runs out of stack space and throws:

RangeError:
Maximum call stack size exceeded

Enter fullscreen mode Exit fullscreen mode

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
};

Enter fullscreen mode Exit fullscreen mode

Objects require memory.

Imagine the heap as a giant warehouse.

Heap

+--------------------+
| name : Ram         |
| age : 25           |
+--------------------+

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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";

Enter fullscreen mode Exit fullscreen mode

The heap allows memory to be allocated dynamically.


Step 6 — Garbage Collection

Suppose we create:

let person = {
    name: "Ram"
};

Enter fullscreen mode Exit fullscreen mode

Memory looks like:

person

↓

Object

Enter fullscreen mode Exit fullscreen mode

Now we write:

person = null;

Enter fullscreen mode Exit fullscreen mode

The variable no longer points to the object.

person

↓

null

Object (still exists)

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

Everything connected to the roots stays alive.

But:

Old Object

(no references)

Enter fullscreen mode Exit fullscreen mode

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"));

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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)