AlgoInsight

Free lesson · JavaScript Basic to Advance

Inside the JavaScript Engine

Easy5 min read

Advanced Track: What's Underneath JavaScript

Module 13 is advanced track content. The material here goes deeper than most JavaScript developers need to work day-to-day. It covers how JavaScript engines actually execute code — parsing, bytecode, JIT compilation, garbage collection, memory layout.

If you're preparing for senior-level interviews, this module is optional. The material in Modules 1-12 covers what most senior interviews probe. If you're preparing for staff-level interviews at FAANG companies, or working on performance-sensitive code, this module is essential. Staff interviews routinely test whether you understand what's happening underneath the language, not just how to use it.

I'm Nikhil. I'm leading the first three Topics — engine architecture, parsing, JIT compilation — because runtime model material has been my territory throughout the course. Emeka takes Topics 13.4-13.6 on memory and performance — his Netflix Data Platform work, handling 8B+ events per day through Node.js, gives him real production authority on the performance side. I return with Arun for the Interview Lens at the end.

A note on scope: this module focuses on V8 — the engine in Chrome, Edge, Node.js, and many other environments. SpiderMonkey (Firefox) and JavaScriptCore (Safari) use similar architectures with different implementation details. The conceptual material applies broadly; the specifics are V8.

Interactive animation: One spec, three machines: V8, SpiderMonkey and JavaScriptCore, the engines behind Chrome, Firefox and Safari.

What an Engine Actually Is

A JavaScript engine takes JavaScript source code and runs it. That's the one-sentence definition. The interesting question is how.

Conceptually, a modern JavaScript engine has four major components:

// You write this:
function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

console.log(fibonacci(20));   // 6765

When the engine runs this, several stages happen:

  1. Parser. Reads the source text, produces an Abstract Syntax Tree (AST).
  2. Interpreter. Converts AST to bytecode, executes it.
  3. JIT compiler. Watches running code for hot paths, compiles them to optimized machine code.
  4. Garbage collector. Reclaims memory from objects no longer in use.

These four work together. The parser turns text into structure. The interpreter runs that structure quickly. The JIT compiler makes hot code faster over time. The garbage collector keeps memory usage from growing forever.

graph LR
    A[Source code] --> B[Parser]
    B --> C[AST]
    C --> D[Bytecode]
    D --> E[Interpreter]
    E --> F[Output]
    E -.->|hot code| G[JIT Compiler]
    G --> H[Machine code]
    H --> F
    I[Garbage Collector] -.->|reclaims| J[Heap memory]

    style B fill:#7c3aed,color:#fff
    style E fill:#3b82f6,color:#fff
    style G fill:#10b981,color:#fff
    style I fill:#f59e0b,color:#fff
This diagram shows the major components of a JavaScript engine. Source code flows into a parser that produces an Abstract Syntax Tree. The AST is converted to bytecode that an interpreter executes. While running, the engine watches for hot code paths — functions called repeatedly. Those hot paths get passed to a JIT compiler that produces optimized machine code, replacing the bytecode for future executions. Throughout all of this, the garbage collector runs in the background reclaiming memory from objects that are no longer reachable.

The V8 Pipeline Specifically

V8's implementation has specific names for these components:

Ignition. V8's interpreter. Takes the AST and produces bytecode, then executes it. Ignition is what runs all JavaScript initially — even before any compilation happens.

TurboFan. V8's optimizing JIT compiler. Takes hot functions identified by Ignition and compiles them to highly-optimized machine code.

Sparkplug. A baseline non-optimizing compiler added in 2021. It sits between Ignition and TurboFan — converts bytecode to machine code quickly without optimizing aggressively. Provides a fast first-compile that's better than interpretation but cheaper than full optimization.

Orinoco. V8's garbage collector. We cover this in detail in Topic 13.4.

When code runs, V8 uses these in sequence: Ignition interprets everything first. Functions called repeatedly get compiled by Sparkplug for a moderate speed boost. Functions that are called many times get fully optimized by TurboFan. If TurboFan-compiled code makes assumptions that turn out to be wrong, it gets deoptimized back to interpreted bytecode.

This tiered approach is a key insight: not all code gets the same treatment. Code that runs once stays interpreted (cheap to compile, expensive to run). Code that runs frequently gets compiled. The compilation cost only pays off if the code runs enough times to recoup it.

Interactive animation: Parse, interpret, observe, optimize: Ignition runs bytecode first, TurboFan compiles hot code, and deopt falls back.

Why Engines Are So Complex

A naive engine could just be an interpreter — read source code, do what it says. That's how the very first JavaScript implementations worked. They were also extremely slow.

JavaScript's design makes simple execution slow:

Dynamic typing. A variable's type can change. The engine can't lay out memory based on assumed types — it has to handle the case where count is sometimes a number and sometimes an object.

Dynamic property access. Objects can have properties added or removed at runtime. The engine can't pre-compute property offsets the way it could in a statically-typed language.

No explicit types. The engine doesn't know whether function add(a, b) will receive numbers, strings, or arrays. It has to handle all possibilities.

Garbage collection. Memory is managed automatically. The engine has to track what's reachable and reclaim what isn't.

Modern engines are complex precisely because they make these dynamic features fast. They observe how code actually runs, make optimistic assumptions, and produce optimized code based on those assumptions. When the assumptions hold, the code runs at near-native speed. When they break, the engine falls back to slower but correct execution.

This is why JavaScript performance is so different from many other languages. Code that looks the same can run at vastly different speeds depending on whether the engine could optimize it. Two functions with identical logic can have 10x performance differences based on what types they receive and how their objects are shaped. We'll dig into the specifics across the rest of this module.

Interactive animation: The same function timed over four batches gets faster as the engine optimizes it - warm-up, in numbers.

The Concept of "Hidden Classes"

A preview of one of the most important V8 internals — covered in detail in Topic 13.3.

V8 doesn't store objects as the dynamic key-value structures they appear to be. Internally, V8 builds hidden classes — type-like descriptions of object shapes. Objects with the same set of properties added in the same order share a hidden class. V8 can then optimize property access for those objects: "all objects with this hidden class have name at offset 0 and age at offset 8."

const olena = { name: "Olena", age: 35 };   // hidden class A
const nikhil = { name: "Nikhil", age: 32 }; // same hidden class A — same shape

const weirdo = { age: 25, name: "Weirdo" }; // different hidden class — different property order

The first two objects share a hidden class because they were created with the same properties in the same order. The third has a different shape because the order differs, so it gets a different hidden class.

This matters enormously for performance. Code that works with consistently-shaped objects is fast. Code that works with objects of varying shapes is slow. We'll dig into the implications in Topic 13.3.

What This Module Will Build

The rest of Module 13:

By the end, you should understand JavaScript performance well enough to make informed decisions about hot code paths, recognize patterns that defeat optimizations, and diagnose memory issues in production code.

What This Module Is NOT

A few things this module deliberately doesn't cover:

Compiler theory in depth. I cover what V8 does at a level useful for application developers and interview prep, not at the level needed to write a compiler. Compiler textbooks exist; this isn't a substitute.

Specifics of other engines. SpiderMonkey, JavaScriptCore, ChakraCore — they're all interesting and worth studying separately. I focus on V8 because it's the most-used and the one staff interviews probe most.

Microbenchmarks. Most "this is N times faster than that" performance claims are misleading without context. I focus on patterns and principles, not contest performance numbers.

The goal is staff-level conceptual depth, not implementation expertise. By the end of Module 13, you'll know what's happening when V8 runs your code, why some patterns are fast and others are slow, and how to think about performance systematically.

Common Pitfalls

Interactive animation: Interpret everything or compile everything? Neither - the engine interprets first and compiles only what proves hot.

Warning: The mental-model traps before we go deeper.

Thinking JavaScript is "interpreted, therefore slow." Modern engines compile hot code to machine code at runtime. JavaScript performance is often competitive with statically-compiled languages — when the engine can optimize it.

Assuming performance advice from one engine applies to others. V8 optimizations don't always match SpiderMonkey or JavaScriptCore. Cross-engine performance work requires testing across engines, not just optimizing for one.

Confusing JavaScript engine internals with the JavaScript language. Hidden classes, inline caches, and TurboFan optimizations are V8 implementation details. They're not in the ECMAScript spec. Code that works correctly should work correctly across engines, even if it performs differently.

Why This Matters in Interviews

Interview: Engine-level questions are staff-tier territory at most companies. Junior and mid-level interviews rarely probe this depth. Senior interviews sometimes probe it for performance-sensitive roles. Staff interviews probe it for nearly every role.

The expected answers don't require you to be a compiler engineer — but you should understand the layers (parser, interpreter, JIT, GC), what each does, and how they interact. Topic 13.7 covers the interview treatment in detail.

Try It

Exercise: Open Node.js and run with V8 introspection flags to see internals at work:

node --print-bytecode --print-bytecode-filter=fibonacci script.js

With a simple Fibonacci function in script.js, this prints the bytecode V8 generates. Don't worry about understanding every instruction — just observe that there's a real, structured representation underneath the source code.

Other flags worth trying:

  • --trace-opt — shows what gets optimized
  • --trace-deopt — shows what gets deoptimized and why
  • --trace-gc — shows garbage collection activity

These are V8 debugging features that production code doesn't normally need, but they're useful for seeing the engine work. The rest of Module 13 explains what you're looking at.

Back to JavaScript Basic to Advance