cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Tiered compilation

Learn how JavaScript engines start with interpreters, watch hot code, then tier up through baseline and optimizing JIT compilers.

By the end, you can
  • 01
    Explain the compile-speed curveCompare interpreters, ahead-of-time compilers, and JIT compilers by startup cost, peak speed, and when each trade-off pays back.
  • 02
    Name the major browser tiersPlace V8's Ignition, Sparkplug, Maglev, and TurboFan beside SpiderMonkey's and JavaScriptCore's current tier names.
  • 03
    Recognize warm-up in real workUse hotness counters, tier-up, background compilation, benchmark warm-up, and Node trace output without writing code for a specific tier.

Start simple, then specialize

JavaScript engines do not know which code deserves expensive machine-code compilation when a page first loads. A tiny analytics helper might run once, while a parser, animation step, or hot render helper might run thousands of times. Tiered compilation is the engine's compromise: start cheap, watch what happens, then spend more compiler effort only where the program proves it is worth it.

Definition

Tiered compilation is a runtime strategy where an engine runs cold JavaScript in a quick tier, collects hotness and feedback, then recompiles frequently executed code into faster tiers with higher compile cost.

Keep the scope sharp. This lesson covers interpreters versus compilers, JIT compilation, the tier names in V8, SpiderMonkey, and JavaScriptCore, and the counters that trigger tier-up. The next lessons go deeper on type feedback, inline caches, speculative optimization, deoptimization, and inlining. For the larger map, revisit Meet the engines, Bytecode & interpreters, and Startup performance.

Interpreter, compiler, and JIT trade-offs

TRADE-OFF

An interpreter starts almost instantly because it can execute bytecode one instruction at a time. That makes it perfect for cold code, but each operation pays dispatch overhead. An ahead-of-time compiler spends time before execution to create fast machine code; that can be excellent for known programs, but it is too slow to do for every function on page load. A just-in-time compiler runs in the middle: compile at runtime when the engine has evidence that the compile cost will pay back.

Real-life analogyImproving a school timetable for busy classes

A school can start with a rough timetable, then improve the plan for the classes that stay busiest. The first plan starts quickly; later improvements repay their work where they help most.

In real life: A rough timetable works on the first day
In JavaScript: Ignition or another interpreter can start cold code quickly
In real life: A better timetable takes time to prepare
In JavaScript: Optimized machine code takes compiler time up front
In real life: Busy classes get the careful improvements
In JavaScript: Only hot functions deserve expensive optimizing tiers
In real life: A quick update helps before the final plan
In JavaScript: A baseline compiler gives faster code without full optimization

Where the analogy stops: A school chooses its timetable deliberately. Engines use counters and feedback while preserving JavaScript semantics.

The compile-time versus run-time curvePop out in the code editor (opens in a new tab)JavaScript
function totalTime(compileCost, callCost, calls) {  return compileCost + callCost * calls;} console.log(totalTime(0, 5, 4));      // interpreter: cheap startconsole.log(totalTime(120, 0.6, 4));  // top tier: too much compile costconsole.log(totalTime(120, 0.6, 400)); // top tier: compile cost pays back

Line 1 models the total cost as compile cost plus call cost. Line 5 is cheap to start: four interpreted calls cost 20 simulated units. Line 6 shows why top-tier code is a bad deal for a short script: the compile cost dominates. Line 7 shows the other side of the curve: hundreds of calls can repay expensive compilation.

Execution tiers are a cost curve, not a ladder every function climbs
TierCompile costRun speedBest fit
InterpreterAlmost no compile delaySlowest per operationCold code, startup, code that runs once
Baseline JITSmall compile costFaster dispatch and simple machine codeFunctions that ran enough to justify quick native code
Mid-tier optimizing JITModerate compile costGood code quicklyHot code where top-tier compile time may still be too expensive
Top-tier optimizing JITHighest compile costBest peak speed after feedbackVery hot loops and long-lived functions

V8: Ignition, Sparkplug, Maglev, TurboFan

V8

V8's current teaching map is Ignition to Sparkplug to Maglev to TurboFan. Ignition is V8's bytecode interpreter. Sparkplug, announced by V8 in 2021, is a non-optimizing baseline compiler that compiles from bytecode directly to machine code. It is designed to be fast enough that short-lived code can benefit before a heavy optimizer would finish.

Maglev, introduced in Chrome M117 in 2023, sits between Sparkplug and TurboFan. V8 describes it as a fast optimizing JIT that uses feedback to make good-enough code quickly. TurboFan remains V8's top-tier optimizing compiler. Historically TurboFan used a Sea of Nodes intermediate representation; V8's 2025 “Land ahoy: leaving the Sea of Nodes” post describes the ongoing move toward the Turboshaft backend, a more control-flow-graph-shaped pipeline for the backend side.

Accuracy note

Chrome and Node can expose different V8 defaults. The Node 22.23.1 build used in this lesson reports Sparkplug and TurboFan enabled by default, but its --v8-options output reports --maglev as default --no-maglev and the Maglev native probe says “Maglev is not enabled.” The browser pipeline still includes Maglev in Chrome releases.

Which tier is this?
  • A function is parsed to bytecode and runs for the first time in V8.
  • V8 translates bytecode to simple machine code without heavy optimization.
  • A hot function needs better code quickly, but not the full top-tier compile bill yet.
  • A very hot function is worth the highest compile effort for peak speed.
  • JavaScriptCore starts with a low-level interpreter before JIT tiers.
Try it yourself
0 of 5 correct

Sort each card by the tier role it describes. The labels are conceptual; exact thresholds are engine choices.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

SpiderMonkey and JavaScriptCore use the same idea

OTHER ENGINES

Tiered compilation is not a V8-only idea. Mozilla's SpiderMonkey source docs describe a tiering engine that increments warmup counters when entering functions and reaching loop headers. Its documented tiers are the C++ interpreter, Baseline Interpreter, Baseline Compiler, and Ion optimizing compiler. The 2020 Warp project changed how Ion gets optimization data: WarpBuilder snapshots bytecode and inline-cache data, then a background thread builds MIR through the Warp pipeline.

JavaScriptCore uses LLInt, Baseline JIT, DFG, and FTL. WebKit's 2016 B3 post explains that FTL switched from LLVM to the B3 backend, and WebKit's 2020 speculation post explains how JavaScriptCore uses speculation to make dynamic JavaScript run quickly. Details differ, but the curve is familiar: cheap start, quick baseline, stronger optimizing tiers.

Tier names across the browser engines
EngineStartBaselineMiddleTop tier
V8IgnitionSparkplugMaglevTurboFan with the Turboshaft backend replacing the old Sea-of-Nodes backend path in current work
SpiderMonkeyC++ interpreter, then Baseline InterpreterBaseline CompilerNone with the Maglev nameIon optimizing compiler built through the Warp pipeline from CacheIR and MIR/LIR
JavaScriptCoreLLIntBaseline JITDFG JITFTL JIT using the B3 backend

Hotness counters and tier-up

COUNTERS

Engines need a signal that code is hot. A simple teaching model is an invocation counter: each function call increments a count, and thresholds request stronger tiers. Real engines combine invocation counts, loop back-edge or interrupt-budget counters, feedback vectors, inline-cache states, and heuristics. Optimizing compilation can happen on background threads so the main thread keeps running older code until the new code is ready.

A tiny invocation-counter modelPop out in the code editor (opens in a new tab)JavaScript
const thresholds = { baseline: 6, mid: 30, top: 90 };let tier = "interpreter";let invocationCount = 0; function scoreCart(items) {  invocationCount += 1;  if (invocationCount === thresholds.baseline) tier = "baseline";  if (invocationCount === thresholds.mid) tier = "mid";  if (invocationCount === thresholds.top) tier = "top";  return { tier, total: items.reduce((sum, item) => sum + item.price, 0) };} for (let call = 1; call <= 8; call += 1) {  const report = scoreCart([{ price: call }, { price: 2 }]);  console.log(call, report.tier, report.total);}

The model starts in interpreter, increments invocationCount on every call, and switches to baseline on call six. Later thresholds stand in for mid-tier and top-tier compilers. The important point is not the number six; it is that tier-up is driven by evidence from running code.

Hotness walkthrough: calls make a function worth compiling
Step 0 of 21Ready
Your turn: follow the blue line

Replay a real simulator that increments a hotness counter, then tiers a hot function from interpretation to baseline code.

Running in
  1. script
Next: line 2
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const thresholds = { baseline: 6, mid: 30, top: 90 };let invocationCount = 0; function scoreCart(items) {  invocationCount += 1;  if (invocationCount === thresholds.baseline) tier = "baseline";  if (invocationCount === thresholds.mid) tier = "mid";  if (invocationCount === thresholds.top) tier = "top";  return { tier, total: items.reduce((sum, item) => sum + item.price, 0) };} for (let call = 1; call <= 8; call += 1) {  const report = scoreCart([{ price: call }, { price: 2 }]);  console.log(call, report.tier, report.total);}
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
A loop interrupt-budget modelPop out in the code editor (opens in a new tab)JavaScript
let tier = "interpreter";let interruptBudget = 18; function crunchLoop(limit) {  let total = 0;  for (let i = 1; i <= limit; i += 1) {    interruptBudget -= 3;    if (interruptBudget <= 0 && tier === "interpreter") {      tier = "baseline"; // a real engine can OSR into compiled loop code      interruptBudget = 18;    }    total += i;  }  return { tier, total };} console.log(crunchLoop(8));

Function calls are not enough. A single call can contain a long-running loop, so engines also check loop progress. On-stack replacement (OSR) lets a running loop switch into compiled code without starting the function over. This lesson shows the idea; a later optimization lesson covers the stack-frame mechanics.

Loop walkthrough: an interrupt budget can trigger OSR
Step 0 of 20Ready
Your turn: follow the blue line

Replay loop hotness with an interrupt budget. This is a teaching model for tier-up and OSR, not a debugger view of a real engine.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
let interruptBudget = 18; function crunchLoop(limit) {  let total = 0;  for (let i = 1; i <= limit; i += 1) {    interruptBudget -= 3;    if (interruptBudget <= 0 && tier === "interpreter") {      tier = "baseline"; // a real engine can OSR into compiled loop code      interruptBudget = 18;    }    total += i;  }  return { tier, total };} console.log(crunchLoop(8));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Compare never optimize, eager optimize, and tiered JIT

PLAYGROUND

The same compile-time curve looks different for a short script and a long session. Never optimizing wins when nothing runs enough to pay back compilation. Eager optimization wastes time compiling cold helpers. Tiering wins the common browser case: start quickly, then spend compiler work on the function that became hot.

Never optimize, optimize eagerly, or tier up?
Strategy comparison modelPop out in the code editor (opens in a new tab)JavaScript
const costs = {  interpreter: { compile: 0, call: 5 },  baseline: { compile: 15, call: 2 },  mid: { compile: 40, call: 1.2 },  top: { compile: 120, call: 0.6 },}; const program = [  { name: "route", calls: 3 },  { name: "format", calls: 160 },  { name: "validate", calls: 3 },  { name: "paint", calls: 3 },  { name: "cleanup", calls: 3 },]; // A real engine uses richer counters, feedback, and background compilation.// This model only shows the compile-time vs run-time trade-off.console.log("never optimize", 860);console.log("optimize eagerly", 703.2);console.log("tiered JIT", 422.6);
Long session: one hot function dominatesTiered JIT wins
Never optimize860.0

compile 0.0 · run 860.0 · top-tier functions 0

  1. interpret every call: 860.0
Optimize eagerly703.2

compile 600.0 · run 103.2 · top-tier functions 5

  1. compile every function: 600.0
  2. run all calls: 703.2
Tiered JIT422.6

compile 175.0 · run 247.6 · top-tier functions 1

  1. format -> baseline: 55.0
  2. format -> mid: 143.0
  3. format -> top: 335.0
Try it yourself

Tiered JIT is fastest for this long program because it pays 175.0 simulated compile units and 247.6 run units. The model is conceptual: real engines use feedback, OSR, background compilers, and different thresholds.

The playground charges simulated compile cost and per-call cost. It teaches the trade-off curve, not a promise about any engine's exact threshold.

The model charges top-tier compile cost for every function in the eager strategy, while the tiered strategy only compiles functions that cross thresholds. Real engines use many more details, but this is the heart of tiered compilation: optimize what evidence says is worth it.

Real Node 22.23.1 and V8 12.4 evidence

EXPERIMENT

V8 exposes internal testing functions when Node runs with --allow-natives-syntax. They are not JavaScript language features, and a normal browser or Node process treats the % syntax as invalid. The lesson tests run child Node processes, remove NODE_TEST_* variables, and assert only stable facts: version strings, V8 option defaults, status bits from V8 12.4's runtime.h, and --trace-opt lines that mention TurboFan compilation.

V8 natives probe, not production JavaScriptJavaScript
// Node 22.23.1 (V8 12.4). Run only in Node with --allow-natives-syntax.function hotAdd(x) {  let total = 0;  for (let i = 0; i < 30; i += 1) total += (x + i) | 0;  return total;} %PrepareFunctionForOptimization(hotAdd);hotAdd(1);hotAdd(2);%OptimizeFunctionOnNextCall(hotAdd);hotAdd(3);console.log(%GetOptimizationStatus(hotAdd));

In V8 12.4, the status bits include kIsFunction = 1 << 0, kOptimized = 1 << 4, and kTurboFanned = 1 << 6. The forced optimization probe is deterministic enough for a lesson test, but it is still an engine-lab tool. Do not ship code that calls native syntax or relies on a particular tier.

Trace commandShell
node --allow-natives-syntax --trace-opt tier-up-probe.mjs
Stable trace shape asserted by the lesson testsText
Node 22.23.1 (V8 12.4)
[manually marking ... hotAdd ... for optimization to TURBOFAN]
[compiling method ... hotAdd ... (target TURBOFAN)]
[completed compiling ... hotAdd ... (target TURBOFAN)]
status bits include kIsFunction, kOptimized, and kTurboFanned
What the Node build showed

The local Node 22.23.1 process reports V8 12.4.254.21-node.56. Its options list Sparkplug and TurboFan enabled by default, Maglev available as a flag but defaulting to --no-maglev, and Maglev background-code flags present. That is why the real forced optimization evidence in this lesson uses TurboFan, while the V8 pipeline section still teaches Maglev as a Chrome tier.

Use tiering knowledge safely

PRACTICAL

Tiered compilation explains why benchmark warm-up matters and why short scripts often never reach a top tier. It does not mean you should write for Sparkplug, Maglev, TurboFan, Ion, or FTL directly. Engine teams change thresholds, tier names, and heuristics. Your job is to write clear code, measure the real interaction, warm up benchmarks, and understand why the first few runs may differ from the steady state.

Practical decisions influenced by tiering
SituationTiering lessonWhat to do
Benchmark warm-upRun setup and warm-up before timing, then report several samples or a median.See Measuring performance before trusting a microbenchmark.
Short scriptsA CLI startup path or one-off page script may never reach TurboFan or another top tier.Optimize startup and parse cost before chasing peak JIT speed.
Hot pathsA frequently called render helper or parser can climb tiers.Measure first, then simplify shapes and inputs when later feedback lessons teach why.
Production adviceDo not depend on % natives, tier flags, or one engine's thresholds.Use portable JavaScript and benchmark the user-visible path.

If a benchmark changes after warm-up, connect that behavior to Measuring performance. If a hot function behaves differently after its inputs change, the next lessons on type feedback and inline caches explain the evidence optimizers consume.

Common misconceptions

“JIT means everything is compiled immediately.”

No. Tiered JITs deliberately avoid expensive compilation for cold code.

“Interpreters are obsolete because compilers are faster.”

Interpreters are a startup optimization. Running soon can be more valuable than running peak-fast later.

“A function in TurboFan will always stay there.”

Optimized code depends on assumptions. Later lessons cover deoptimization when assumptions fail.

“All engines use V8's tier names.”

SpiderMonkey and JavaScriptCore have their own names, thresholds, and implementation details.

“Native syntax is a production performance API.”

It is a V8 testing interface behind flags. Production code should measure behavior, not force tiers.

Ideas that are easy to confuse
IdeaMeansDoes not mean
JITRuntime compilation guided by evidence.A promise that every function compiles immediately.
InterpreterA fast-start execution tier for cold code.A useless fallback that modern engines avoid.
Optimized codeMachine code guarded by assumptions and feedback.Permanent code that can never deoptimize.
V8 native syntaxA flag-gated testing interface for experiments.Portable production JavaScript.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upRead the short-run trade-off

What number prints for the four-call interpreter case?

Starter codePop out in the code editor (opens in a new tab)JavaScript
function totalTime(compileCost, callCost, calls) {
  return compileCost + callCost * calls;
}
console.log(totalTime(0, 5, 4));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeFind the first baseline tier-up

    At which call does the lesson simulator first switch from interpreter to baseline?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const thresholds = { baseline: 6, mid: 30, top: 90 };
    console.log(thresholds.baseline);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeDecode the forced V8 status

      Which V8 status bit name identifies top-tier TurboFan code?

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeName the loop tier-switch mechanism

        What abbreviation names the mechanism that can switch a long-running loop to compiled code mid-execution?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeChoose production-safe behavior

          Should production application code call V8 native optimization functions?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          // Should app code call %OptimizeFunctionOnNextCall in production?

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            8 QUESTIONS

            Answer with the compile-time curve, tier names, and hotness counters in mind.

            Lesson quiz · 8 questionsScore: first tries count
            1. Question 1 of 8Why do modern JavaScript engines use tiered compilation?

              Choose an answer to see the explanation.

            2. Question 2 of 8What does this trade-off snippet print first?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function totalTime(compileCost, callCost, calls) {
                return compileCost + callCost * calls;
              }
              console.log(totalTime(0, 5, 4));

              Choose an answer to see the explanation.

            3. Question 3 of 8Which V8 tier is the non-optimizing baseline compiler introduced in 2021?

              Choose an answer to see the explanation.

            4. Question 4 of 8Which statement best describes Maglev in V8's current pipeline?

              Choose an answer to see the explanation.

            5. Question 5 of 8What do SpiderMonkey warmup counters check?

              Choose an answer to see the explanation.

            6. Question 6 of 8What does this loop-budget model print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              let tier = "interpreter";
              let budget = 6;
              for (let i = 0; i < 3; i += 1) {
                budget -= 3;
                if (budget <= 0) tier = "baseline";
              }
              console.log(tier);

              Choose an answer to see the explanation.

            7. Question 7 of 8Why should production code avoid V8 native syntax such as %GetOptimizationStatus?

              Choose an answer to see the explanation.

            8. Question 8 of 8Which practical benchmark habit follows from tiered compilation?

              Choose an answer to see the explanation.

            Key takeaways

            • Interpreters start quickly; compilers can run faster after paying compile time; JITs choose at runtime what is worth compiling.
            • V8's modern map is Ignition, Sparkplug, Maglev, and TurboFan, with Turboshaft work replacing the old Sea-of-Nodes backend path.
            • SpiderMonkey and JavaScriptCore use different tier names but the same economic idea: spend more compiler effort on hotter code.
            • Hotness comes from function entries, loop progress, feedback, and thresholds; OSR lets loops switch tiers while already running.
            • Warm up benchmarks, expect short scripts to stay in cheap tiers, and never write production code for a specific engine tier.

            Remember the one-liner.
            Tiered compilation is the engine's pay-as-you-go plan: start cheap, watch real execution, and spend compiler effort only where it pays back.

            Up next: Type feedback & feedback vectors.

            CompleteFrontend Clear concepts. Working examples.