completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Optimizing compilers

Learn how JavaScript optimizing compilers build SSA, graphs, engine pipelines, and background threads that turn hot code into fast machine code.

By the end, you can
  • 01
    Explain why optimizers build an IRSay why an engine rebuilds a hot function in its own private form, the IR, before it makes machine code.
  • 02
    Use SSA and graph wordsUse a small function to explain SSA names, phi nodes, control-flow graphs, and Sea of Nodes graphs.
  • 03
    Compare real enginesGive the job of V8's Maglev, TurboFan, and Turboshaft, Firefox's WarpMonkey, and Safari's DFG, FTL, and B3.

An optimizer rebuilds the function first

A JavaScript engine is the program inside a browser or Node that runs your code. A function that runs very often is called hot. For hot functions, the engine uses its strongest compilers. To compile means to turn code into another form before running it. Machine code is the form the computer's processor runs directly.

A strong compiler does not jump straight from your source text to machine code. First it rebuilds the function in its own private form. That form is called an intermediate representation, or IR. In the IR, the compiler can:

  • give each value its own name,
  • join up the two paths of an if,
  • attach feedback: the notes the engine kept about the values each step has seen, such as "this was always a small whole number",
  • choose the machine instructions.

None of this changes what your JavaScript means.

Definition

An optimizing compiler is a JIT ("just in time") compiler for hot code: it compiles while the program is running. It spends extra time compiling. It builds one or more IR graphs: drawings made of parts joined by lines. It uses feedback to fit the code to the values it has seen. Then it makes faster machine code that gives exactly the same JavaScript result.

The Baseline compilers lesson covered quick compilers such as V8's Sparkplug. This lesson picks up from there. It shows the strongest compilers in three engines:

  • V8, the engine in Chrome and Node: Maglev and TurboFan.
  • SpiderMonkey, the engine in Firefox: WarpMonkey.
  • JavaScriptCore, the engine in Safari: DFG and FTL.

The next lesson, Speculation & deoptimization, covers the checks that protect the compiler's guesses. This lesson covers IRs, SSA, graphs, the steps inside each engine, and compiling on a second thread.

SSA: every value gets its own name

SSA

In JavaScript, a variable can change many times. That makes it hard for a compiler to know which value a line reads. So many compilers use SSA, short for single static assignment. In SSA, each name in the IR gets a value exactly once. When your code changes a variable, the compiler makes a new name instead.

Your JavaScript variables still change as normal. Only the compiler's private copy uses the new names.

A discount that may changePop out in the code editor (opens in a new tab)JavaScript
function finalPrice(isMember) {  let discount = 5;  if (isMember) {    discount = 20;  }  return 100 - discount;} console.log(finalPrice(true));console.log(finalPrice(false));

In plain words, line by line:

  1. Line 2 sets discount to 5.
  2. Line 4 changes it to 20, but only for a member.
  3. Line 6 subtracts whichever value discount holds now.
  4. Lines 9 and 10 call the function both ways. They print 80 for a member and 95 for someone who is not a member.

In SSA, the compiler gives each value of discount its own name:

  • discount0 = 5 is the first value.
  • discount1 = 20 is the member value.
  • discount2 is the value after the if.

After the if, the code can arrive by two paths. A phi node (say "fie") is a special IR step at the place where the paths join. It picks the value from the path that ran: discount1 for a member, and discount0 for anyone else.

Three boxes. Box discount0 holds 5, for a buyer who is not a member. Box discount1 holds 20, for a member. Arrows from both lead to box discount2, which holds 5 or 20. A phi picks one.not a member5discount0a member20discount15 or 20discount2phi picks one
Each box is filled once and never changed. After the if, the phi fills discount2 with the value from the path that ran.
Real-life analogyLabeled lunch boxes filled once

A cook fills each labeled lunch box once and never changes what is inside. Two boxes may be sent to the same table, but only one of them comes. The table uses the box that came.

In real life: Each lunch box gets its own label
In JavaScript: Each SSA value gets a new name, such as discount0
In real life: A filled box is never emptied and refilled
In JavaScript: An SSA name gets its value once, then is only read
In real life: Two boxes may come to the same table
In JavaScript: A phi node picks the value from the path that ran
In real life: The table uses the box that came
In JavaScript: Later code reads one name, discount2

The step-through below runs a small converter written in plain JavaScript. It is a model, not V8 itself. It really builds the SSA lines shown beside the code.

Step through: give each value a new name
Step 0 of 13Ready
Your turn: follow the blue line

Step through a small, real SSA converter. It is a model written in JavaScript, not output from V8.

Running in
  1. script
Next: line 2
function convertDiscountToSsa() {  const nextVersion = {};  function name(base) {    const version = nextVersion[base] ?? 0;    nextVersion[base] = version + 1;    return base + version;  }  const total = name("total");  ssa.push(total + " = parameter total");  const regularDiscount = name("discount");  ssa.push(regularDiscount + " = 5");  const memberDiscount = name("discount");  ssa.push(memberDiscount + " = 20");  const mergedDiscount = name("discount");  ssa.push(mergedDiscount + " = phi(" + memberDiscount + ", " + regularDiscount + ")");  const payable = name("payable");  ssa.push(payable + " = " + total + " - " + mergedDiscount);  return ssa;} console.log(convertDiscountToSsa().join(" | "));
CallStoreChangeResultBlue line: runs next. 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.

Maglev: a fast SSA compiler in V8

V8

Maglev is one of V8's optimizing compilers. V8's Maglev article calls it a simple JIT compiler built on SSA. It arrived in Chrome version 117 (M117). It sits between two other V8 compilers:

  • Sparkplug, the quick baseline compiler from the last lesson,
  • TurboFan, V8's strongest and slowest compiler.

Maglev makes code that is faster than Sparkplug's. It makes that code much sooner than TurboFan can.

The model below shows how feedback changes the plan. Two words first. An object's shape is its layout: which property names it has, in which order. Smi is short for "small integer", a small whole number.

A small model of how Maglev plansPop out in the code editor (opens in a new tab)JavaScript
const stableFeedback = { shape: "CartShape", priceType: "smi" };const changedFeedback = { shape: "OtherShape", priceType: "mixed" }; function maglevPlan(feedback) {  if (feedback.shape !== "CartShape" || feedback.priceType !== "smi") {    return ["GenericPropertyLoad", "GenericAdd", "Return"];  }  const graph = [];  graph.push("CheckShape " + feedback.shape);  graph.push("CheckSmi price");  graph.push("LoadField price");  graph.push("Int32Add");  graph.push("Return");  return graph;} console.log("stable", maglevPlan(stableFeedback).join(" -> "));console.log("changed", maglevPlan(changedFeedback).join(" -> "));

In plain words, line by line:

  1. Lines 1 and 2 stand in for feedback from the earlier steps. stableFeedback says every cart had the same shape and every price was a smi. changedFeedback says other shapes and other kinds of values showed up.
  2. Line 5 checks the feedback. If it is not the steady kind, line 6 returns a generic plan: steps that work for any value, but slowly.
  3. Lines 9 to 13 build the special plan. Check the shape. Check that the price is a smi. Read the price straight from its known place. Add with a fast whole-number add. Return.
  4. Line 16 prints stable and then the special plan: CheckShape CartShape -> CheckSmi price -> LoadField price -> Int32Add -> Return.
  5. Line 17 prints changed and then the generic plan: GenericPropertyLoad -> GenericAdd -> Return.

A check step such as CheckShape is a guard. If the guess turns out wrong, the optimized code stops and hands the work back to slower code. The next lesson covers that. Real Maglev starts from bytecode and feedback, and builds a graph in SSA form. This model only teaches the words.

The table below places each optimizing compiler. Read each row as one compiler, its job, and how to think about it.

Where the optimizing compilers fit
CompilerIts jobHow to think about it
MaglevA fast SSA compiler in V8.It builds an SSA graph from bytecode and feedback, then makes fairly fast code quickly.
TurboFanV8's strongest compiler.It spends more time compiling to make the fastest code. Its JavaScript backend now uses Turboshaft.
TurboshaftV8's newer backend.It makes the machine code. It uses a CFG-shaped IR in place of TurboFan's old Sea of Nodes backend.
WarpMonkeyFirefox's optimizing compiler.It uses bytecode and saved CacheIR steps to build MIR, then optimized code.
FTL with B3Safari's top step.FTL does the high-level work for hot code, and B3 makes its machine code.
What Node really does

The lesson tests run Node 22, which uses V8 version 12.4. They list V8's settings with node --v8-options. The list shows three things:

  • Maglev is off by default in this Node: --maglev defaults to --no-maglev.
  • TurboFan is on.
  • Concurrent recompilation, which means compiling on a second thread, is on.

Even with the --maglev flag, a test that asks for Maglev gets the message "Maglev is not enabled". So the real trace output later in this lesson comes from TurboFan.

TurboFan: two ways to draw a program

GRAPHS

TurboFan is V8's strongest optimizing compiler. Like most compilers, it keeps its IR as a graph. The parts of the graph are called nodes, and the lines between them are called edges. There are two common shapes:

  • A control-flow graph, or CFG, cuts the code into basic blocks. A basic block is a run of steps with no branch in the middle. Edges between blocks show which block can run next.
  • A Sea of Nodes graph has no fixed blocks at first. Each node is one operation. Edges show which values a node needs, which effects (such as a property write) must happen first, and which branch it belongs to.

For years, TurboFan was known for using Sea of Nodes. In 2025, V8's article "Land ahoy: leaving the Sea of Nodes" said that TurboFan's JavaScript backend now uses Turboshaft. The backend is the last part of a compiler, the part that makes machine code. Turboshaft uses the more usual CFG shape.

One program, two graph shapes
One small program, drawn two waysPop out in the code editor (opens in a new tab)JavaScript
function chooseBonus(a, b, flag) {  const left = a * 2;  const right = b * 2;  const picked = flag ? left : right;  return picked + 1;} console.log(chooseBonus(3, 4, true));console.log(chooseBonus(3, 4, false));
Control-flow graphblocks
  1. Entry block: work out left and right, then check flag
  2. Then block: picked is left
  3. Else block: picked is right
  4. Merge block: return picked + 1

The program still prints 7 and 9. The IR is only the compiler's tool. It does not change what your JavaScript means.

Try it yourself

Control-flow graph: A CFG puts the steps into blocks and joins the blocks with lines in the order they can run. You can read it from top to bottom. This is a model. It does not look inside your browser's engine.

Switch between the two drawings of the same program. The program prints the same thing either way. Only the compiler's private drawing changes.
Two drawings of chooseBonus. Left, a control-flow graph: an entry block leads to a then block and an else block, and both lead to a merge block. Right, Sea of Nodes: a * 2, flag, and b * 2 all feed picked, and picked feeds picked + 1.control-flow graphentrythenelsemergeSea of Nodesa * 2flagb * 2pickedpicked + 1
Left: blocks of code joined in the order they run. Right: each value points to the values that use it. Both drawings describe the same function.

There is one catch. In JavaScript, a * 2 on line 2 can run your own code. For example, an object can have a valueOf method that JavaScript calls to turn the object into a number. Turning a value into another type like this is called coercion. So the compiler cannot move line 2 or line 3 around freely.

The playground's Sea of Nodes view starts after two checks. Feedback said a and b were numbers, and number checks proved it. Only then are the two multiplications free to move. A CFG view shows blocks and branches. A Sea of Nodes view shows which values depend on which.

The table compares the two shapes. Read each row as one shape, what it looks like, and why a compiler picks it.

Two graph shapes that are easy to mix up
Graph shapeWhat it looks likeWhy a compiler uses it
Control-flow graph (CFG)Basic blocks, joined by lines for each branch and jump.The order the steps run in is easy to see.
Sea of NodesSingle operations, joined by the values, effects, and branches they depend on.Work that depends on nothing else can move freely until the compiler picks the final order.
TurboshaftV8's JavaScript backend now uses a CFG-shaped IR.It keeps most of the speed and makes the compiler simpler to build and quicker to run.
The program both graphs describePop out in the code editor (opens in a new tab)JavaScript
function chooseBonus(a, b, flag) {  const left = a * 2;  const right = b * 2;  const picked = flag ? left : right;  return picked + 1;} console.log(chooseBonus(3, 4, true));console.log(chooseBonus(3, 4, false));

Line 2 doubles a. Line 3 doubles b. Line 4 uses flag to pick one of them. Line 5 adds 1. The program prints 7 (3 × 2 + 1) and then 9 (4 × 2 + 1).

Where these facts come from

WarpMonkey turns cache notes into MIR

SPIDERMONKEY

SpiderMonkey is the engine in Firefox. Its optimizing compiler is WarpMonkey, or Warp for short. Warp starts from inline caches, or ICs: the small saved shortcuts from the Inline caches lesson. SpiderMonkey writes each IC in a simple format called CacheIR. One saved list of CacheIR steps is called a stub.

When Warp optimizes a function, three things happen, in order:

  1. WarpOracle takes a snapshot: a frozen copy of the function's bytecode and its useful IC data.
  2. WarpBuilder and the WarpCacheIRTranspiler turn that CacheIR into MIR. MIR is SpiderMonkey's middle-level IR. A transpiler turns code from one format into another.
  3. Later, MIR becomes LIR, a lower-level IR close to machine code, and then machine code.
A small model: CacheIR steps become MIR stepsPop out in the code editor (opens in a new tab)JavaScript
const cacheIR = ["GuardShape", "LoadFixedSlotResult"]; function warpToMir(cacheIR) {  return cacheIR.map((op) => {    if (op === "GuardShape") return "MGuardShape";    if (op === "LoadFixedSlotResult") return "MLoadFixedSlot";    return "MUnsupported";  });} console.log(warpToMir(cacheIR).join(" -> "));

In plain words, line by line:

  1. Line 1 is the IC's record. GuardShape checks the object's shape. LoadFixedSlotResult reads a property from a fixed place inside the object.
  2. Lines 4 to 6 go through each step and swap in the matching MIR name. Line 7 handles any step the model does not know.
  3. Line 11 prints MGuardShape -> MLoadFixedSlot.

Real Warp also uses what it saw about calls. Inlining means copying a called function's body into the caller, so no call is needed when the code runs. In trial inlining, Warp tries this for hot called functions before it builds the final code.

Where these facts come from

The Firefox CacheIR docs describe CacheIR stubs, snapshots, and the WarpCacheIRTranspiler. The SpiderMonkey overview names WarpMonkey as the top optimizing step. It says Warp turns bytecode and IC data into MIR, then LIR, then machine code.

JavaScriptCore: DFG, FTL, and B3

JSC

JavaScriptCore is the engine in Safari. It uses different names:

  • The DFG (Data Flow Graph) JIT is an optimizing compiler.
  • The FTL (Faster Than Light) JIT is the stronger top step.
  • B3, the Bare Bones Backend, is the part of FTL that makes the final machine code. WebKit's B3 article says B3 replaced LLVM in that job. LLVM is a large, general-purpose compiler toolkit.
JavaScriptCore's steps, in orderPop out in the code editor (opens in a new tab)JavaScript
const jscTiers = ["LLInt", "Baseline JIT", "DFG", "FTL (B3 backend)"]; function describeJscPipeline(tiers) {  return tiers.join(" -> ");} console.log(describeJscPipeline(jscTiers));

In plain words, line by line:

  1. Line 1 lists the steps in order. LLInt is the low-level interpreter, where code starts. Baseline JIT is the quick compiler.
  2. Line 4 joins the names with arrows.
  3. Line 7 prints LLInt -> Baseline JIT -> DFG -> FTL (B3 backend).

B3 is written inside FTL's entry because it is part of FTL, not a step after it. When code moves from one step to the next is up to each engine. It never changes what your JavaScript means.

JavaScriptCore's steps in a row: LLInt starts, Baseline JIT is quick, DFG optimizes, and FTL is the top step. Under FTL sits B3, the backend that FTL uses.LLIntstartsBaseline JITquickDFGoptimizesFTLtop stepB3FTL's backend
A hot function moves from left to right. B3 is not a fifth step. It is the part inside FTL that makes the final machine code.

The table puts the three engines side by side. Read each row as one engine and its optimizing compilers.

Three engines and their optimizing compilers
EngineMiddle optimizerTop optimizer and backendWhat to remember
V8MaglevTurboFan, with Turboshaft as its backendIgnition and Sparkplug supply bytecode and feedback. The tested Node 22 (V8 12.4) keeps Maglev off by default.
SpiderMonkeyWarpMonkeyMIR, turned into LIR, then machine codeWarp snapshots the saved CacheIR steps and turns them into MIR. Trial inlining uses the calls it saw.
JavaScriptCoreDFGFTL, with B3 as its backendDFG does the high-level work. FTL uses B3 to make the final machine code.
Where these facts come from

WebKit's B3 article says FTL uses DFG for the high-level work and B3 as its backend. WebKit's article on speculation explains why these steps make guesses about JavaScript values, and why they must be ready to recover when a guess is wrong.

Compiling on a second thread

THREADS

A thread is one line of work that the computer runs. Your JavaScript runs on the main thread. Concurrent compilation means the engine builds optimized code on a background thread, a second line of work. Meanwhile, the main thread keeps running the older code.

The compiler works from:

  • a snapshot, a frozen copy of the facts it needs,
  • the feedback,
  • a list of things that must stay true for the new code to be correct.

The engine switches to the new code only when it is ready and safe. This switch is called installing the code.

Real-life analogyA cook plans while lunch is served

Lunch keeps being served while one cook plans a faster order for the dishes. When the plan is ready, the kitchen starts using it. The meal itself does not change.

In real life: Lunch keeps going out to the tables
In JavaScript: The main thread keeps running the baseline code
In real life: One cook plans a faster serving order
In JavaScript: The optimizer builds code on a background thread
In real life: The new order starts when the plan is ready
In JavaScript: Optimized code is installed after it finishes compiling
In real life: The meal stays the same
In JavaScript: The JavaScript result is the same before and after
Step through: calls keep running while code compiles
Step 0 of 8Ready
Your turn: follow the blue line

Step through a model of compiling on a second thread. It shows what each thread does. It does not look inside your browser's engine.

Running in
  1. script
Next: line 13
function buildConcurrentTimeline(callsBeforeReady = 3) {  const events = [];  events.push("main: run baseline call 1");  events.push("background: compile optimized graph");  for (let call = 2; call <= callsBeforeReady; call += 1) {    events.push("main: run baseline call " + call);  }  events.push("main: install optimized code when it is ready");  events.push("main: run optimized call " + (callsBeforeReady + 1));  return events;} 
CallStoreChangeResultBlue line: runs next. 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.
Two rows. The main thread runs call 1, call 2, and call 3 with baseline code, then installs the new code, then runs call 4 with optimized code. The background thread compiles while calls 2 and 3 run, and an arrow goes from compile to install.main threadcall 1call 2call 3baseline codeinstallcall 4optimized codebackground threadcompile
Calls 2 and 3 do not wait. They run the old code while the background thread compiles. The new code is installed only when it is ready.

The table splits the work into three parts. Read each row as one part, its job, and why it matters.

Who does what during concurrent compilation
PartIts jobWhy it matters
Main threadIt keeps running the baseline code or older optimized code.The page keeps responding, instead of freezing while a compile finishes.
Background threadIt builds the optimized graph and the machine code.It works from a snapshot and checks, because the program keeps running and changing.
Install pointThe engine switches to the new code when it is ready and safe.The JavaScript result is the same before and after the switch.

Now the real thing. The lesson tests run the code below in Node with --trace-opt. That flag makes V8 print a line each time it starts optimizing a function.

A hot function for the Node trace testJavaScript
function hotSum(x) {  let total = 0;  for (let i = 0; i < 200; i += 1) total = (total + x + i) | 0;  return total;} for (let n = 0; n < 20000; n += 1) hotSum(n);
Trace lines the lesson tests checkText
Default:
[compiling method ... hotSum ... (target TURBOFAN), mode: ConcurrencyMode::kConcurrent]

With --no-concurrent-recompilation:
[compiling method ... hotSum ... (target TURBOFAN), mode: ConcurrencyMode::kSynchronous]

With --allow-natives-syntax --maglev:
Maglev is not enabled.
V8 settings the lesson tests checkJavaScript
// Node only. Run: node --v8-options
// The lesson tests check only these settings:
// --maglev defaults to --no-maglev in Node 22 (V8 12.4).
// --turbofan and --concurrent-recompilation are on by default.

In the first block, line 1 defines hotSum. Line 3 gives it a loop of work. Line 7 calls it 20,000 times, so it becomes hot. In the trace lines:

  • (target TURBOFAN) means TurboFan compiles the function.
  • ConcurrencyMode::kConcurrent means it compiles on a background thread. This is the default.
  • With --no-concurrent-recompilation, the mode becomes ConcurrencyMode::kSynchronous. The main thread waits while the function compiles.
  • With --allow-natives-syntax --maglev, a test that asks for Maglev prints Maglev is not enabled.

The last block is a note, not code to run. It lists the V8 settings from node --v8-options that the tests check.

The tests check only these parts, because they are the same on every run. They do not check timings, memory addresses, or exact version numbers.

Use this knowledge safely

Optimizing compilers explain two things you may notice. Code often gets faster after it has run a while; this is called warm-up. And a small change to hot code can change its speed. But these compilers are not a set of hidden switches for you to flip. In your own code:

  • Write clear JavaScript.
  • Keep your objects the same shape when that is natural.
  • Measure what the user waits for before you change code to make it faster.

When you measure speed:

  • Warm up a small speed test (a microbenchmark) first. Then report many runs. Do not trust the first, slow run.
  • Use the browser's performance tools to find the slow part of the page before you think about compilers.
  • Choose simple, readable code over clever code written for one named compiler.
  • When the optimizer surprises you, the next lesson on speculation and deoptimization often explains why.
Sort the optimizer words
  • Writes to discount become discount0, discount1, and a phi.
  • V8's newer backend, which uses a CFG shape in place of the old Sea of Nodes backend.
  • Firefox's saved inline-cache steps, which Warp turns into MIR.
  • Build optimized code while the main thread keeps running the old code.
  • The part of Safari's FTL that makes the final machine code.
Try it yourself
0 of 5 correct

Put each card under the kind of idea it describes.

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

Common misconceptions

"SSA means my JavaScript variables cannot change."

No. SSA is only how the compiler names values in its private copy. Your JavaScript variables change exactly as before.

"Sea of Nodes and CFGs are just drawings."

No. They are two ways to build the IR. The shape decides which compiler steps are easy to write and quick to run.

"If Node lists the Maglev flag, Node uses Maglev."

No. The tested Node 22 (V8 12.4) lists the flag, but Maglev is off. A test that asks for Maglev gets "Maglev is not enabled".

"Concurrent compilation runs my function on a second thread."

No. Only the compiling happens on the second thread. Your function still runs on the main thread, as normal JavaScript does.

The table below lists four terms. Read each row as a term, what it means, and what it does not mean.

Terms that sound alike but mean different things
TermWhat it meansWhat it does not mean
IRThe compiler's private copy of the program.A JavaScript feature you can see or use in your code.
SSAA naming rule: each name gets a value only once.A rule that your JavaScript variables cannot change.
Phi nodeA step that picks a value where two paths join.A function that your JavaScript calls.
Concurrent compilationCompiling can happen on a second thread.Your function runs on two threads at once.

Practice exercises

6 EXERCISES
Exercise 1 · Warm-upPredict the member price

What number does this print for a member?

Starter codePop out in the code editor (opens in a new tab)JavaScript
function finalPrice(isMember) {
  let discount = 5;
  if (isMember) discount = 20;
  return 100 - discount;
}
console.log(finalPrice(true));

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

    Exercise 2 · PracticeName the step that joins the paths

    In SSA, which step picks between the member discount and the regular discount after the if?

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

      Exercise 3 · PracticeChoose the graph shape

      Which graph shape cuts the code into basic blocks?

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

        Exercise 4 · PracticeFind Warp's middle IR

        Warp turns the saved CacheIR steps into which middle-level IR?

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

          Exercise 5 · PracticeFix the order of the timeline

          This timeline installs the optimized code too early. When should the install step happen?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const timeline = [
            "main: install optimized code",
            "background: compile optimized graph"
          ];
          console.log(timeline.join(" -> "));

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

            Exercise 6 · ChallengeApply it to a real app

            A checkout page feels slow after someone rewrote some of its code. What should you do before you change code for Maglev, TurboFan, Warp, or FTL?

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

              Quiz: check your understanding

              8 QUESTIONS

              Use the IR words, the engine names, and the two threads from this lesson.

              Lesson quiz · 8 questionsScore: first tries count
              1. Question 1 of 8Why does an optimizing compiler build an IR before it makes machine code?

                Choose an answer to see the explanation.

              2. Question 2 of 8What does this branch example print first?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                function finalPrice(isMember) {
                  let discount = 5;
                  if (isMember) discount = 20;
                  return 100 - discount;
                }
                console.log(finalPrice(true));

                Choose an answer to see the explanation.

              3. Question 3 of 8In SSA, what does a phi node do?

                Choose an answer to see the explanation.

              4. Question 4 of 8Which sentence matches V8's description of Maglev?

                Choose an answer to see the explanation.

              5. Question 5 of 8What does this timeline model print last?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const events = [];
                events.push("main: baseline call");
                events.push("background: compile");
                events.push("main: install optimized");
                console.log(events.at(-1));

                Choose an answer to see the explanation.

              6. Question 6 of 8What did V8 change in 2025 with Turboshaft?

                Choose an answer to see the explanation.

              7. Question 7 of 8What does Firefox's Warp do with CacheIR?

                Choose an answer to see the explanation.

              8. Question 8 of 8What is the safest way to use what you know about optimizers in a real app?

                Choose an answer to see the explanation.

              Key takeaways

              • An optimizing compiler first rebuilds a hot function as an IR, its own private form. Then it makes faster machine code.
              • In SSA, each value gets a name exactly once. A phi node picks the value from the path that ran.
              • V8 uses Maglev for quick optimizing and TurboFan, with its Turboshaft backend, for the strongest code. Node 22 (V8 12.4) keeps Maglev off by default.
              • SpiderMonkey's Warp turns CacheIR into MIR. JavaScriptCore uses DFG, then FTL, with B3 as FTL's backend.
              • Concurrent compilation builds optimized code on a background thread while the main thread keeps running the old code.

              Remember the one-liner.
              An optimizing compiler rebuilds hot code as a graph, makes it faster, and switches to the new code only when it gives the same result.

              Up next: Speculation & deoptimization. You'll see the guards that check the compiler's guesses, and what happens when a guess is wrong.

              CompleteFrontend Clear concepts. Working examples.