DEV Community

Cover image for My Journey Into JavaScript Engines...
Omaima Ameen
Omaima Ameen

Posted on Edited on

My Journey Into JavaScript Engines...

Before we begin, I want to take a moment to thank this community❤️

A few days ago, I shared a post reflecting on my first year on DEV. I honestly didn't expect the amount of love, encouragement, and support it received. Seeing that post get picked as a DEV Gem made it even more special.

I'm genuinely grateful to everyone who took the time to read it, share their experiences, leave such amazing comments, or simply follow along. It means more than you might think!!

And now, here's me starting year two by exploring something I've been curious about for a while. Hopefully, there'll be many more things to learn and share along the way.

Thank you for being part of the journey. ❤️

Okay, now let's get back to what this post is actually about!!

I've been writing JavaScript for a while now. Declaring variables, calling functions, creating objects, manipulating the DOM, and building little projects that sometimes turn into much bigger ones.

For a long time, most of my attention was on making the code work. If the function returned the right output and the application behaved as expected, I was happy.

but lately, I've found myself getting curious about what's happening underneath all that code.

What actually happens after I hit Run? How does the computer understand the JavaScript I've written? And what is happening behind the scenes before that output finally appears on my screen?

That curiosity led me down the rabbit hole of JavaScript engines, and I thought it would be fun to share some of what I'm learning along the way.

So, let's start with a simple question:

What actually happens when we run JavaScript?

How does our machine actually understand and execute JavaScript code?

The browser doesn't magically understand the JavaScript we write. Something needs to process that code, make sense of its structure, and execute it.

That something is a JavaScript engine.

Let's break down what happens behind the scenes.

1. First, What Exactly Is a JavaScript Engine?

A JavaScript engine is a program responsible for parsing and executing JavaScript code.

Think of it as the component that takes the JavaScript we write and makes it executable by the machine.

Some popular JavaScript engines are:

  • V8 ~ used by Google Chrome and Node.js.
  • SpiderMonkey ~ used by Firefox.
  • JavaScriptCore ~ used by Safari.

For this article, we'll focus on V8.

One important distinction: a JavaScript engine isn't the same thing as a JavaScript runtime.

For example, when JavaScript runs in a browser, the browser provides additional APIs such as the DOM, fetch(), and timers. These aren't all part of the JavaScript engine itself.

The engine handles JavaScript execution; the surrounding environment provides additional capabilities.

2. How Does JavaScript Code Get Understood...

Let's say we have this:

const price = 100;
const quantity = 3;

function calculateTotal(price, quantity) {
  return price * quantity;
}

console.log(calculateTotal(price, quantity));
Enter fullscreen mode Exit fullscreen mode

Before executing this code, V8 needs to understand its structure.

This is where parsing comes in.

The engine first breaks the source code into smaller meaningful pieces called tokens. This process is known as tokenisation.

For example:

const price = 100;
Enter fullscreen mode Exit fullscreen mode

The engine identifies things like:

  • const — a keyword
  • price — an identifier
  • = — an assignment operator
  • 100 — a numeric literal
  • ; — a semicolon

Once these pieces are identified, the parser checks whether they follow JavaScript's grammar rules.

And if they don't?

const price = ;
Enter fullscreen mode Exit fullscreen mode

Well, JavaScript has a problem. 😭

The parser detects a syntax error because the assignment is incomplete.

But when everything is valid, the engine builds something called an Abstract Syntax Tree (AST).

3. What Is an AST, Anyway...🤔

Think of an AST as a structured representation of your code., instead of looking at JavaScript as a collection of characters, the engine represents its structure in a tree-like format.

For example:

const total = 100 + 50;
Enter fullscreen mode Exit fullscreen mode

A simplified representation might look something like this:

VariableDeclaration
        |
   VariableDeclarator
      /       \
 Identifier  BinaryExpression
   total       /       \
           100         50
Enter fullscreen mode Exit fullscreen mode

The actual AST contains more details, but this gives us an idea of what's happening.

The important thing is that the engine now has a structured representation of what the code means.

We're getting somewhere!

But here's the next question:

Does V8 immediately convert this AST into highly optimised machine code?👀

Nope, and this is where things get interesting.

4. Enter Ignition: The Interpreter

V8 uses an interpreter called Ignition.

Ignition takes the parsed representation of JavaScript and generates bytecode that can be executed by the interpreter.

Now, bytecode is neither the original JavaScript source code nor native machine code.

It's an intermediate set of instructions designed for the JavaScript engine.

Think of it as a more execution-friendly representation of our program.

For example, imagine we have:

function add(a, b) {
  return a + b;
}
Enter fullscreen mode Exit fullscreen mode

Conceptually, the engine needs instructions to:

  1. Load the values of a and b.
  2. Perform the addition.
  3. Return the result.

The actual V8 bytecode is more detailed, but that's the general idea.

Ignition allows V8 to start executing code without first spending time performing expensive optimisations on everything and that brings us to an interesting engineering decision.

5. Why Not Compile Everything Immediately?

Imagine opening a website.

It contains hundreds of JavaScript functions. Some might run once. Some might never run at all. Others might execute thousands of times.

Would it make sense to spend time heavily optimising every single function before the website even starts working?

Probably not.

That would waste time and resources on code that might barely execute.

V8 takes a more practical approach.

It can start executing JavaScript through Ignition while collecting information about how the code behaves.

For example:

function calculateTotal(price, quantity) {
  return price * quantity;
}

console.log(calculateTotal(100, 3));
console.log(calculateTotal(200, 2));
Enter fullscreen mode Exit fullscreen mode

Try running this in Node.js.

You'll get:

300
400
Enter fullscreen mode Exit fullscreen mode

Nothing surprising here.

But imagine this function is called repeatedly in an application.

V8 can collect runtime feedback about how the function behaves and identify code that executes frequently, this frequently executed code is often called hot code.

and once code becomes hot, V8 may decide that it's worth compiling it further to make execution faster.

That's where its compilation pipeline becomes more interesting.

6. A Quick Look at What's Coming Next

V8 doesn't rely on just one execution strategy.

Its modern compilation pipeline includes multiple tiers:

  • Ignition: interprets bytecode.
  • Sparkplug: quickly compiles bytecode into baseline machine code.
  • Maglev: performs mid-tier optimisation.
  • TurboFan: performs more advanced optimisation.

Not every function necessarily passes through every tier. V8 makes decisions based on runtime behaviour and the potential benefits of optimisation.

We'll explore these components properly in the next article.

For now, I just wanted to establish one important idea:

JavaScript execution isn't simply a matter of translating the entire program once and running it.

The engine can start quickly, observe how the program behaves, and optimise the code that deserves that extra effort.

And honestly, that's a pretty interesting piece of engineering.

7. Let's Put the Journey Together

Here's a simplified picture of what we've explored:

      JavaScript Source
              |
              v
         Tokenisation
              |
              v
            Parsing
              |
              v
   Abstract Syntax Tree (AST)
              |
              v
    Ignition Bytecode Generation
              |
              v
      Bytecode Execution
              |
              v
    Runtime Feedback & Profiling
              |
              v
     Further Compilation
       and Optimisation
Enter fullscreen mode Exit fullscreen mode

This is a simplified mental model, not a strict sequence that every function must follow.

8. What I Took Away From This

Before looking into JavaScript engines, I mostly thought about JavaScript from the developer's side: writing functions, handling events, making API calls, and building applications.

But there's a whole lot happening underneath those familiar lines of code.

The engine has to understand our source code, represent its structure, execute it, observe its behaviour, and make decisions about optimisation.

In the next article, I'll explore hot code, runtime feedback, and the different compilation tiers behind V8 - Sparkplug, Maglev, and TurboFan.

See you in the next one! 🌸


Further Reading

Top comments (0)