Meet the engines
Explore V8, SpiderMonkey, JavaScriptCore, smaller engines, shared pipelines, hosts, embedders, and tools for engine work.
- 01Name the engine familiesPlace V8, SpiderMonkey, JavaScriptCore, Hermes, QuickJS, and embedded engines in the ecosystem.
- 02Trace the common pipelineFollow source text through tokens, an AST, bytecode, interpretation, feedback, optimization, deoptimization, and GC.
- 03Separate engine from environmentDecide whether a feature belongs to ECMA-262, the host, or the embedder that wires an engine into an app.
Set the engine map for the stage
You already learned the beginner split in Engines, runtimes & hosts, then met Node, Deno, Bun, and edge runtimes in Runtimes. This expert stage looks inside the engine itself. It starts with the big picture so later lessons can zoom into scanners, parsers, bytecode, inline caches, garbage collection, and tiered compilation without redefining every moving part.
A JavaScript engine is the implementation that follows ECMA-262: it scans source text, parses syntax, creates internal code, runs that code, optimizes hot paths when it can, deoptimizes when assumptions fail, and manages JavaScript memory with a garbage collector. The host around it decides what outside world your program can touch.
Engine knowledge helps you read performance profiles and mysterious runtime messages, but the vehicle still matters. A DOM error is often a browser-host issue; a deopt trace is engine internals; a build failure may be neither.
- In real life: The engine turns fuel into motion
- In JavaScript: The JavaScript engine turns source text into running behavior
- In real life: The car body has seats, lights, dashboard, and doors
- In JavaScript: The host has DOM, timers, files, modules, permissions, and UI
- In real life: A mechanic can inspect engine timing without redesigning the road
- In JavaScript: You can study parsing, bytecode, JIT, and GC without pretending they define every browser API
- In real life: The same engine family can power different models
- In JavaScript: V8 appears in Chrome, Edge, Node.js, and Deno with different host surfaces
Where the analogy stops: Car engines are physical and stable for years. JavaScript engine internals change quickly, and hosts add policy, security, rendering, networking, and scheduling around the engine.
The previous lesson was Finding memory leaks. That lesson used tooling from the outside. This one names the inside. The next lesson, Scanning source text, follows the very first arrow: characters becoming tokens.
V8, SpiderMonkey & JavaScriptCore
BIG THREEMost web developers meet three production engine families. V8 is built by the V8/Chromium project and runs in Chrome, Edge, Node.js, and Deno. SpiderMonkey is Mozilla's JavaScript engine in Firefox. JavaScriptCore is WebKit's engine in Safari and WebKit, and Bun uses JavaScriptCore for its runtime.
Tier names below are a snapshot verified against primary sources in September 2026. Treat them as a map, not a contract: engine teams rename tiers, replace backends, and move work between interpreter, baseline, and optimizing tiers.
| Engine | Who builds it | Where you meet it | Tier names to recognize | Careful reading |
|---|---|---|---|---|
| V8 | Google; open source through the V8 project | Chrome, Edge and other Chromium browsers, Node.js, Deno | Ignition, Sparkplug, Maglev, TurboFan | Fast-moving tier names; inspect the V8 docs for current details. |
| SpiderMonkey | Mozilla | Firefox | Baseline Interpreter, Baseline JIT/Compiler, Warp/Ion | Mozilla docs describe WarpMonkey as the top optimizing tier and Ion as the MIR/LIR backend lineage. |
| JavaScriptCore | Apple/WebKit | Safari/WebKit, WebKit-based views, Bun | LLInt, Baseline JIT, DFG JIT, FTL JIT | WebKit's posts use LLInt, Baseline, DFG, and FTL as the tier names. |
| Hermes | Meta/React Native | React Native apps | AOT bytecode for app bundles, interpreter-focused mobile startup | React Native builds JavaScript to Hermes bytecode during release builds. |
| QuickJS / QuickJS-ng | Fabrice Bellard and Charlie Gordon; community fork QuickJS-ng | Small embedders, CLIs, tests, constrained products | Small interpreter with bytecode and reference-counting GC | Great for embedding, not a browser replacement. |
Do not memorize every tier yet. The point for this first lesson is orientation: engines usually start with a lower-latency interpreter or baseline tier, collect feedback, then spend more compile time on hotter code. Later lessons unpack why each tier exists and why optimized code must still be able to bail out.
Hermes, QuickJS & embedded engines
The big three are not the whole ecosystem. Smaller engines make different trade-offs: startup over peak throughput, tiny memory over browser compatibility, Java embedding over DOM work, or research and conformance over market share. These engines are especially useful when JavaScript is embedded inside an app, a device, a test harness, or another language.
| Engine | Typical home | What to remember |
|---|---|---|
| Hermes | React Native default engine | Optimizes startup and memory; release builds compile JavaScript to Hermes bytecode ahead of time. |
| QuickJS / QuickJS-ng | Tiny embeddable C engine | A small interpreter with fast startup, broad ECMAScript support, and reference counting with cycle removal. |
| Boa | Rust implementation | Experimental lexer, parser, interpreter, runtime crates, and Test262-driven conformance work. |
| LibJS | SerenityOS / Ladybird ecosystem | A browser-oriented JavaScript engine built as part of the SerenityOS project. |
| JerryScript | Internet of Things | Targets very constrained devices such as microcontrollers with tens of kilobytes of RAM. |
| Moddable XS | Microcontroller apps | Implements modern JavaScript for the Moddable SDK and focuses on small memory and flash budgets. |
| GraalJS | GraalVM / Java embedding | ECMAScript-compliant runtime implemented on GraalVM with Java interoperability and standalone launches. |
Embedded engines often skip a browser DOM and may avoid a heavyweight optimizing JIT, but they still care about conformance, startup, predictable memory, and safe host bindings. A microcontroller engine and a browser engine solve different production problems.
The shared pipeline
STEP THROUGHEngines differ internally, but they share a recognizable path: source text becomes scanner tokens, tokens become a parse tree or AST, functions may be lazily parsed, executable bytecode is produced, an interpreter starts running it, inline caches and type feedback record what actually happened, optimizing JITs compile hot paths, guards deoptimize back to lower tiers when assumptions fail, and the garbage collector runs alongside everything to reclaim unreachable objects.
Step through the shared engine pipeline in miniature: source text, tokens, AST, bytecode, interpreter result.
script
const tokens = ["num:2","+/-:+","num:3","*:*","num:4"];const astRoot = "BinaryExpression";const bytecode = ["PUSH 2","PUSH 3","PUSH 4","MUL","ADD"];const result = 14;console.log(result);Line 2 is the scanner story. Line 3 is the parser story. Line 4 is a teaching bytecode, not V8's Ignition bytecode, SpiderMonkey bytecode, or JavaScriptCore bytecode. It is deliberately tiny so you can see the same shape before later lessons introduce real engine-specific details.
Inspect real tokens, a real ESTree AST, and toy bytecode
LIVEThis playground runs in your browser. Acorn produces the token stream and an ESTree-compatible AST excerpt. A small compiler in this lesson accepts only arithmetic expressions and emits stack bytecode. The bytecode is a model you can step through; it is not copied from any production engine.
const sourceText = "2 + 3 * 4";const tokens = ["num:2","+/-:+","num:3","*:*","num:4"];const astRoot = "BinaryExpression";const bytecode = ["PUSH 2","PUSH 3","PUSH 4","MUL","ADD"];const result = 14;console.log(result);Tokens
num2 0-1+/-+ 2-3num3 4-5** 6-7num4 8-9
ESTree AST excerpt
{
"type": "BinaryExpression",
"start": 0,
"end": 9,
"operator": "+",
"left": {
"type": "Literal",
"start": 0,
"end": 1,
"value": 2,
"raw": "2"
},
"right": {
"type": "BinaryExpression",
"start": 4,
"end": 9,
"operator": "*",
"left": {
"type": "Literal",
"start": 4,
"end": 5,
"value": 3,
"raw": "3"
},
"right": {
"type": "Literal",
"start": 8,
"end": 9,
"value": 4,
"raw": "4"
}
}
}Toy bytecode and stack
PUSH 2stack [2]PUSH 3stack [2, 3]PUSH 4stack [2, 3, 4]MULstack [2, 12]ADDstack [14]
Acorn produced 5 tokens, the AST root is BinaryExpression, and the teaching bytecode returns 14.
Try (7 - 1) / 3 and 5 * (2 + 6). Notice that parentheses and precedence change the AST shape before bytecode is emitted. That is why the scanner and parser lessons come before bytecode and JIT lessons in this stage.
Interpreters, JIT compilers, feedback, deopt, and GC
OPTIMIZEInterpreters are good at getting code running quickly. JIT compilers spend CPU time to generate machine code for hot code. Optimizing JITs rely on speculation: if calls have seen numbers, object shapes, and stable property accesses, optimized code can guard those assumptions and run faster. If a later call breaks an assumption, the engine deoptimizes to a lower tier that can handle the general case correctly.
Step through how feedback can let an engine optimize speculatively, and why a later type change may deoptimize.
script
return a + b;} const feedback = [];feedback.push(typeof 1 + "+" + typeof 2);const first = add(1, 2);const optimizedFor = feedback[0];const later = add("3", 4);const outcome = typeof later === "number" ? "stay in optimized code" : "deopt to a lower tier";console.log(first);console.log(outcome);Garbage collection is not a final stage after execution. It is a partner beside the pipeline. Values, objects, functions, closures, bytecode, feedback vectors, optimized code, and host references all affect memory pressure. That is why memory lessons and engine lessons meet again when you read heap snapshots or investigate leaks.
Learn the pipeline to debug and measure better, not to micro-optimize blindly. Engine internals change, and a tiny rewrite can help one tier, hurt another, or be irrelevant beside network, rendering, or algorithmic cost.
Engine vs host vs embedder
BOUNDARIESECMA-262 defines the language: syntax, values, objects, execution semantics, and built-ins such as Promise and Array.prototype.map. Hosts define how JavaScript meets the outside world: HTML defines the browser event loop, timers, the DOM integration, and module loading behavior; Node defines process, file modules, package resolution, and its runtime policies. Embedders call engine APIs to create isolates, contexts, native bindings, snapshots, and globals.
| Examples | Owner | How to think about it |
|---|---|---|
let, functions, objects, Array.prototype.map, Promise, JSON, globalThis | ECMA-262 language | Engines implement these semantics. |
DOM, window, document, rendering, user events, many browser module-loading details | Browser host standards such as HTML and DOM | The browser embeds an engine and provides page APIs. |
Timers, event loop integration, fetch, streams, modules, workers | Host specifications and runtime choices | Browsers and server runtimes often share shapes but differ in policy and limits. |
process, node:fs, node:path, package resolution | Node.js host | These are not JavaScript language features. |
| Isolates, contexts, native bindings, snapshots, command-line flags | Embedder-engine API | The application embedding the engine decides how to create worlds and expose host objects. |
PromiseArray.prototype.mapdocument.querySelectorrequestAnimationFramenode:fs/promisesprocess.versions.v8- create a V8 Isolate
- expose a native object to JS
Sort each card by who provides it. The explanations are the important part; several cards are intentionally close.
The short probe below uses only standardized globals, so it runs in the browser editor and in Node. It is intentionally not a DOM or Node-only example.
console.log(typeof Array.prototype.map);console.log(typeof globalThis);console.log(typeof Promise);An embedder sketch looks different because it is not ordinary page JavaScript. It is the surrounding application creating an engine instance, a context, and host objects before evaluating user code.
// Pseudocode: embedders call engine APIs from C++/Rust/Java.const isolate = createIsolate();const context = isolate.createContext({ console });context.evaluate("console.log(1 + 2)");Exploring engines with d8, jsvu & eshost
TOOLSEngine tools are powerful and version-specific. d8 is V8's developer shell. jsvu installs recent engine binaries without compiling them yourself. eshost-cli runs the same snippet across registered engines. node --v8-options lists V8 flags exposed by your Node build. Test262 is the conformance suite engine implementers use to check ECMAScript behavior.
$ node --versionv22.23.1$ node -p "process.versions.v8"12.4.254.21-node.56$ node --v8-options | grep -- --print-bytecode --print-bytecode (print bytecode generated by ignition interpreter)To find Chrome's V8 version, open chrome://version in that browser and read the JavaScript/V8 entry. Do not assume Chrome, Node, Deno, and an installed d8 shell all ship the same V8 revision.
# V8's developer shell after building V8 or installing a binary with jsvuv8 --print-bytecode demo.js # Install current engine shells without compiling them yourselfnpx jsvu --engines=v8,spidermonkey,javascriptcore,quickjs,hermes,xs # Register shells and compare behavior across enginesnpx eshost-cli --configure-jsvueshost -e 'print(1 + 2)' # Run the official ECMAScript conformance suite when building an enginenpm test -- path/to/test262The commands are non-runnable in the lesson page because the browser editor is not your shell and most engine binaries are not installed here. The tests do run real Node commands and prove that this Node build reports V8 12.4 and exposes --print-bytecode.
Where engine knowledge helps
You rarely need to write code for a specific JIT tier, but this model helps in production work. It tells you where to look when startup is slow, why syntax errors appear before code runs, why a memory leak can keep closures and DOM nodes alive, why one odd call shape can spoil a hot path, and why a browser-only API fails in a server runtime.
- When a page starts slowly, ask how much source must be downloaded, scanned, parsed, compiled, and executed before interaction.
- When a profile shows a hot function, measure before changing code and look for algorithmic or allocation wins first.
- When a heap snapshot shows retained objects, connect those references to closures, host objects, and GC roots.
- When code crosses browser, Node, Deno, Bun, or embedded contexts, separate language semantics from host APIs.
Keep Runtime overview, Manuals & specs, and The event loop nearby. They explain the host side that engine internals deliberately do not own.
Common misconceptions
- “V8 is the JavaScript standard.” V8 is one implementation. ECMA-262 is the language specification.
- “A JIT always makes code faster.” JITs trade compile time and memory for speed on hot paths. Cold code may stay interpreted or baseline-compiled.
- “Bytecode means the same thing in every engine.” Engines use different bytecode formats and tiers. This lesson's bytecode is only a teaching model.
- “The engine owns timers and the DOM.” Hosts wire scheduling, rendering, files, networking, and global objects around the engine.
- “Garbage collection fixes leaks automatically.” GC reclaims unreachable objects. If your code or host objects still reference data, it remains reachable.
- “Engine flags are portable production APIs.” Flags are implementation tools and can change, disappear, or behave differently across versions.
| Word | Means | Does not mean |
|---|---|---|
| Engine | ECMAScript implementation plus memory and execution machinery | A full browser or server runtime |
| Host | The environment that embeds the engine and defines outside-world APIs | The parser or JIT by itself |
| Runtime | A complete program such as Node, Deno, Bun, a browser tab, or an edge worker | Only the language spec |
| Embedder | The application code that creates engine isolates or contexts and exposes native objects | Ordinary JavaScript application code |
Practice exercises
5 EXERCISESUse the live pipeline. What is the first token kind for 2 + 3 * 4?
const tokenValues = ["num:2", "+", "num:3", "*", "num:4"];
console.log(tokenValues.join(" | "));The first token is the number literal 2, labeled num by acorn.
For the toy compiler's 2 + 3 * 4 output, type the final instruction.
const bytecode = ["PUSH 2", "PUSH 3", "PUSH 4", "MUL", "ADD"];
console.log(bytecode.at(-1));The final instruction is ADD, because the AST root is the outer +.
Who provides document.querySelector: the engine, the browser host, Node, or Test262?
document.querySelector comes from the browser host's DOM, not the engine alone.
Which Node property did this lesson use to show 12.4.254.21-node.56?
Use process.versions.v8 in Node to read the embedded V8 version string.
Why might optimized code built for number addition bail out after a later call with "1"?
function add(a, b) {
return a + b;
}
console.log(add(1, 2));
console.log(add("1", 2));A numeric + assumption can fail when a later call receives a string. Real optimized code uses guards and deoptimizes when the feedback no longer matches.
Quiz: engines and their boundaries
8 QUESTIONSAnswer with the pipeline and layer boundaries in mind. Code answers are real JavaScript outputs, not guesses.
Question 1 of 8Which statement best describes a JavaScript engine?
Choose an answer to see the explanation.
Question 2 of 8Which engine and tier list matches Chrome and Node as of this lesson's sources?
Choose an answer to see the explanation.
Question 3 of 8What does the acorn-backed toy pipeline compute for
2 + 3 * 4?Choose an answer to see the explanation.
Question 4 of 8What does this code print?
Read the code, then predictfunction add(a, b) { return a + b; } console.log(add(1, 2)); console.log(add("1", 2));Choose an answer to see the explanation.
Question 5 of 8Which API belongs to the browser host, not ECMA-262?
Choose an answer to see the explanation.
Question 6 of 8What does this browser-safe probe print in modern browsers and Node?
Read the code, then predictconsole.log(typeof Array.prototype.map); console.log(typeof globalThis); console.log(typeof Promise);Choose an answer to see the explanation.
Question 7 of 8What is
d8useful for?Choose an answer to see the explanation.
Question 8 of 8Why should engine knowledge not turn into premature micro-optimization?
Choose an answer to see the explanation.
Key takeaways and what comes next
- V8, SpiderMonkey, and JavaScriptCore are the big browser engine families; Hermes, QuickJS, Boa, LibJS, JerryScript, XS, and GraalJS serve other embedding needs.
- The shared path is source text to tokens, parser/AST, bytecode, interpreter, feedback, JIT tiers, possible deoptimization, and GC alongside the whole process.
- ECMA-262 defines language semantics; hosts define DOM, timers, files, modules, event loops, and globals; embedders create engine contexts and native bindings.
- Use d8, jsvu, eshost-cli, Node's V8 flags, and Test262 as investigation tools, not as portable production dependencies.
- Engine knowledge is practical when debugging startup, memory, and performance, but measurements matter more than folklore.
| Coming lesson | What it will unpack |
|---|---|
| Scanning source text | How bytes and UTF-16 code units become tokens, including ambiguous / and escaped identifiers. |
| Parsing and ASTs | Grammar, early errors, ESTree, cover grammars, and why parsers sometimes reparse. |
| Lazy parsing and startup | Why engines delay work for functions that might never run. |
| Scope analysis and bytecode | How declarations, closures, and bytecode records prepare code for execution. |
| Tiered compilation, feedback, inline caches, GC | How engines speed up hot code, bail out safely, and reclaim memory. |
Further reading from primary sources
- V8 Ignition docs, Sparkplug, Maglev, and TurboFan.
- Mozilla SpiderMonkey source docs for parser, bytecode, Baseline, Warp, bailout, and GC overviews.
- WebKit's FTL JIT overview and JavaScriptCore speculation post.
- React Native Hermes docs, QuickJS, QuickJS-ng, JerryScript, and Moddable XS.
- V8 d8 docs, jsvu, eshost-cli, and Test262.
Up next: Scanning source text, where the first character stream becomes tokens.