completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Speculation & deoptimization

Learn how JavaScript engines optimize for observed types, guard those assumptions, and deopt safely when runtime values change.

By the end, you can
  • 01
    Explain speculative optimizationDescribe how the engine makes fast code for the values it has seen, and keeps a way back for values that don't fit.
  • 02
    Tell the two kinds of deopt apartTell a guard that fails right away (an eager deopt) from a watchpoint that marks fast code out of date (a lazy deopt).
  • 03
    Investigate without overdoing itRead a --trace-deopt line, spot a deopt loop, and pick simple habits that help real code you have measured.

Make the usual case fast, keep a way back

The engine is the program that runs your JavaScript. Chrome and Node use one called V8. When a function runs many times, the engine calls it hot. It then tries to turn the hot function into fast machine code: instructions the computer's processor runs directly.

There is a problem. In JavaScript, the same variable can hold a number now and a string later. Fast machine code works best when it knows the kind of value in advance. So the engine makes a bet.

  • It looks at the values a function has seen so far.
  • It makes fast code for those values only.
  • It adds a quick check, in case a different value shows up later.

This bet is called speculation. If the bet turns out wrong, the engine throws the fast code away and goes back to its slower, general code. That step back is called deoptimization, or deopt for short.

The main idea

Speculative optimization means the engine makes fast code for the values it has seen, and checks each time that the values still fit. When a check fails, the engine deoptimizes: it leaves the fast code and finishes the work in its slower, general code. Your program still gets the correct answer. Only the speed changes.

Two earlier lessons explain how the engine collects its notes about values: Type feedback and Inline caches. The lesson before this one, Optimizing compilers, explains the part that makes the fast code. This lesson is about the checks that keep the fast code safe, and what happens when one fails.

Real-life analogyFavorite snacks on the top shelf

You keep your favorite snack on the top shelf, because you pick it most days. One day a guest asks for something else. You don't hand them your usual snack. You go to the kitchen and make what they asked for. It takes longer, but they get the right thing.

In real life: You often pick the same snack
In JavaScript: The engine's notes show the same kind of value each time
In real life: You keep it on the top shelf
In JavaScript: The engine makes fast code for that kind of value
In real life: A guest asks for something else
In JavaScript: A check fails, because a new kind of value arrived
In real life: You go to the kitchen and make it
In JavaScript: The engine deopts to its general code, and the answer is still right

Speculative optimization: a fast path with a check

A speculative plan has three parts:

  • Feedback: notes about which values reached one operation, like one +. The place where the engine keeps the notes for one operation is called a feedback slot.
  • Fast code made for those values. This is the fast path.
  • A guard: a quick check at the start of the fast code. It asks, "Do these new values still fit?" If not, the engine takes the slow path: its general code, which works for any value.

V8 has a short name for small whole numbers, such as 10 or -3: Smis, short for "small integers". Fast code for Smis can add two numbers in one quick processor step.

Step through: fast path, guard, deopt
Step 0 of 6Ready
Your turn: follow the blue line

Replay a small model of speculation: the notes lead to fast code with a guard, and strings make it deopt safely.

Running in
  1. script
Next: line 1
observe(feedback, 10, 2);const optimized = compileWithGuards(feedback);optimized(12, 3);optimized("12", "3");
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.

This replay is a small model written in JavaScript. It does not look inside your browser's engine. Press Step to go line by line:

  1. Line 1 makes an empty feedback slot for the total + fee operation.
  2. Line 2 adds 10 and 2. The notes now say SignedSmall: V8's name for "only Smis so far".
  3. Line 3 makes fast code from those notes, with a guard in front.
  4. Line 4 calls the fast code with 12 and 3. The guard passes, and the result is 15.
  5. Line 5 calls it with the strings "12" and "3". The guard fails. The engine leaves the fast code, and the slow path joins the two strings into "123".
Two calls to the same fast code. optimized(12, 3): the check passes, the fast path made for small integers runs, and the result is 15. optimized("12", "3"): the check fails, the slow path that works for anything runs, and the result is "123".optimized(12, 3)check passesfast pathmade for small integers15optimized("12", "3")check failsslow pathworks for anything"123"
The guard checks each call first. Small integers take the fast path. Strings fail the check, so the engine goes back to the slow path, and the answer is still right.

The answer "123" is exactly what JavaScript always gives for "12" + "3". A deopt changes which code the engine runs. It never changes what your code means.

Numbers every time: the + always addsPop out in the code editor (opens in a new tab)JavaScript
function totalCents(price, tax) {  return price + tax;} console.log(totalCents(100, 8));console.log(totalCents(250, 20));

Line 2 has the + we care about. Lines 5 and 6 call totalCents with two numbers each time, so this + always adds numbers. The code prints 108, then 270.

This does not mean "never use strings". It means: try not to make one busy + switch between adding numbers and joining strings, unless your program really needs both.

The feedback lattice: notes only get wider

The engine sorts its notes for one + into a few states. The states form a ladder, from narrow to wide. A narrow state fits only a few kinds of values. A wide state fits more. This ladder is called a lattice. When a new kind of value arrives, the state moves up. It does not move back down.

V8 calls the notes for an operation with two sides, like a + b, BinaryOp feedback. In the V8 version inside Node 22 (V8 12.4), its states for + include None, SignedSmall, Number, NumberOrOddball, and Any. V8 calls undefined, null, true, and false oddballs. The real compiler splits some cases further, but this ladder is a safe way to picture it.

Read each row of the table from left to right: the state's name, the values the + has seen, and the fast code the engine can make.

The feedback ladder for one + operation, from narrow to wide
Feedback stateWhat has the + seen?What fast code can the engine make?
NoneNothing yet. The + has not run.None yet. The engine first collects notes while running its slower code.
SignedSmallOnly small integers, which V8 calls Smis, like 1 + 2.Fast code that adds two Smis and checks that the sum still fits.
NumberAt least one number with a decimal part, like 1.5.Fast code that adds any two numbers, not only whole ones.
NumberOrOddballNumbers mixed with undefined, null, true, or false.Fast code that first turns those odd values into numbers, the way JavaScript does.
AnyValues that need different jobs, such as adding numbers and joining strings.General code that works for any value. A later compiler may still split some cases apart.
A ladder of five feedback states, from None at the bottom to Any at the top. 1 + 2 moves it to SignedSmall. 1.5 + 2.25 moves it to Number. undefined + 1 moves it to NumberOrOddball. "7" + "8" moves it to Any.AnyNumberOrOddballNumberSignedSmallNone"7" + "8"undefined + 11.5 + 2.251 + 2startfits anything
The playground's four calls, from the bottom up. Each new kind of value moves the + one step up the ladder. It never steps back down.
Checked in V8's source

V8 keeps this list of states in an enum (a fixed list of names) called BinaryOperationHint, in the file src/objects/type-hints.h. It holds kNone, kSignedSmall, kNumber, kNumberOrOddball, kString, kBigInt, kBigInt64, and kAny. This lesson's test also runs Node 22 and asks its V8 to print the real notes for small integers, numbers, and oddballs.

The next code runs only in Node, not in the browser editor. It needs the flag --allow-natives-syntax. That flag turns on V8's own test functions, whose names start with %. %DebugPrint prints what V8 knows about a function, including its feedback.

Node only: print the real notes for three + operationsJavaScript
function onlySmi(a, b) { return a + b; }for (let i = 0; i < 30; i += 1) onlySmi(i, i + 1);console.log("ONLY_SMI");%DebugPrint(onlySmi); function smiThenDouble(a, b) { return a + b; }for (let i = 0; i < 30; i += 1) smiThenDouble(i, i + 1);smiThenDouble(1.5, 2.25);console.log("SMI_DOUBLE");%DebugPrint(smiThenDouble); function numberOddball(a, b) { return a + b; }for (let i = 0; i < 30; i += 1) numberOddball(undefined, i);console.log("ODDBALL");%DebugPrint(numberOddball);
  1. Lines 1 to 4: onlySmi adds small integers 30 times. V8 prints BinaryOp:SignedSmall.
  2. Lines 6 to 10: smiThenDouble also gets 1.5 and 2.25 on line 8. V8 prints BinaryOp:Number.
  3. Lines 12 to 15: numberOddball adds undefined to a number. V8 prints BinaryOp:NumberOrOddball.
Feed values into one +
The calls the playground can makeJavaScript
const slot = createSlot("BinaryOp slot #0");feed(slot, 1, 2);          // SignedSmallfeed(slot, 1.5, 2.25);     // Numberfeed(slot, undefined, 1);  // NumberOrOddballfeed(slot, "7", "8");     // Any
Fast codeno fast code yet
None state0 deoptsno fast code turned off

No calls yet. Start with small integers, then try strings or undefined.

Step 0 of 7None

Feed values into one +. The state starts at None and moves up as new kinds of values arrive. After 3 deopts, the model stops making fast code.

This is a small model. It does not look inside your browser's engine. The results are real JavaScript answers; the notes, checks, and deopts follow the lesson's model.

Press "Run the next value" to make seven calls in a set order, or press one call button at a time. Then press Reset and try strings first. A + that has only ever joined strings gets its own state, String. But once the same + has seen both numbers and strings, this model moves it to Any.

Guards, checks, and watchpoints

Fast code can rely on two kinds of facts. Some facts are about the values in this call. Others are about things outside the function.

Checks inside the fast code test the values of this call, right where they are used. Three common ones:

  • A Smi check asks, "Is this value still a small integer?"
  • A map check asks, "Does this object still have the same layout?" V8 gives every object a hidden map: a record of which properties it has, in which order.
  • A bounds check asks, "Is this index still inside the array?"

A watchpoint works differently. It is a note the engine keeps about a fact outside the function, such as "this prototype won't change". (A prototype is the object that other objects borrow shared properties from.) The fast code does not check the fact on every call. Instead, if some other code changes that fact, the engine marks every piece of fast code that relied on it as out of date. "Watchpoint" is a word several engines use. V8's own messages call these code dependencies. The exact details differ from engine to engine.

Read each row of the table as: "this kind of check sits here, asks this, and this happens if it fails."

The checks and watchpoints that keep fast code safe
Kind of checkWhere is it?What does it ask?What happens if it fails?
Smi checkInside the fast code.Is this value still a small integer?An eager deopt, right away.
Map checkInside the fast code, before it reads a property.Does this object still have the same layout (map)?An eager deopt, right away.
Bounds checkInside the fast code, around an array read.Is the index inside the array, and does the array still hold the same kind of items?Usually an eager deopt, or a slower built-in way.
Watchpoint on a layoutIn a note kept outside the function.Will objects with this layout stay the same?The fast code is marked out of date. The engine picks when it is left.
Watchpoint on a propertyIn a note about a property or a prototype.Will this property keep its value?The fast code is marked out of date. The engine picks when it is left.
Real-life analogyChecking a classroom list

A teacher checks each child's name at the classroom door. That check happens right there, for one child. Sometimes the school sends a whole new class list. Nobody is at the door at that moment. The teacher just knows the old list is out of date, and uses the new one at the next check.

In real life: The teacher checks one child's name at the door
In JavaScript: A guard checks this call's value, right now
In real life: A name is not on the list, so the teacher stops that child at once
In JavaScript: A guard fails, so the engine leaves the fast code at once
In real life: The school sends a new class list
In JavaScript: Other code changes a fact that the fast code relied on
In real life: The teacher puts the old list away and uses the new one next time
In JavaScript: The engine marks the fast code out of date and stops using it later

Eager and lazy deopts

A deopt can happen at two different times:

  • An eager deopt happens right at the failing guard. The fast code is in the middle of running, and the value it has is the wrong kind. It cannot go on, so the engine leaves at once.
  • A lazy deopt happens later. First, other code changes a fact the fast code relied on, and the engine marks the fast code out of date. The code is left afterwards, at a safe point: a moment the engine picks where it can switch safely.

Leaving fast code in the middle of a call takes some work. Each running call has a frame: a record of its variables and the line it has reached. The engine keeps extra notes about where each variable sits inside the fast code. It uses those notes to rebuild the frame for its slower code, with every variable in place.

Step through: marked now, slow path later
Step 0 of 6Ready
Your turn: follow the blue line

Replay a small model of a watchpoint: a change marks the fast code out of date, and a later call takes the slow path. Real engines pick the exact moment.

Running in
  1. script
Next: line 1
const plan = compilePriceReader(menu);plan({ subtotal: 100 });changeTaxRate(menu, 0.12);plan({ subtotal: 100 });
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.

This replay is a small model too. Press Step to go line by line:

  1. Line 1 makes a menu with a tax rate of 0.05.
  2. Line 2 makes fast code that relies on that tax rate. A watchpoint now protects the fact.
  3. Line 3 runs the fast code. It prints 105, which is 100 × 1.05.
  4. Line 4 changes the tax rate to 0.12. It does not call the fast code. It only marks it out of date.
  5. Line 5 calls again. The model sees the mark and uses the slow path. It prints 112.00000000000001: about 112, with a tiny rounding error from how computers store decimals.
Three steps of the watchpoint replay. Line 3: the fast code uses tax 0.05 and prints 105. Line 4: the tax is set to 0.12 and the fast code is marked out of date. Line 5: the next call takes the slow path and prints about 112.line 3fast codeuses tax 0.05prints 105line 4out of datetax set to 0.12fast code markedline 5slow pathnext callprints about 112
Line 4 does not call the fast code. It only marks it as out of date. The switch to the slow path happens later, on the next call.

Real engines choose their own safe point. A V8 message about a code dependency proves the fast code was marked. It does not tell you the exact moment the code was left.

Read each row of the table as: "this kind of deopt starts like this, shows this in the trace, and often comes from these causes." A trace is a log the engine can print about what it is doing.

Eager and lazy deopts side by side
Kind of deoptWhat starts it?What does the trace say?Common causes
Eager deoptA guard in the fast code fails right now.V8's trace line contains bailout (kind: deopt-eager, reason: ...).The wrong kind of value, a different object layout, an index outside the array, or too few notes.
Marked out of date, then a lazy deoptA change elsewhere marks the fast code out of date. It may be left later, at a safe point the engine picks.V8 prints marking dependent code ... for deoptimization with reason: code dependencies. That line alone doesn't say when the code was left.A changed prototype, a changed object layout, or a changed property the code relied on.
Deopt from a long loopA long loop switched to fast code while it was running, and later leaves that fast code.The On-stack replacement lesson shows how a running loop switches to fast code.Long loops, and loops that end early.
Sort the speculation and deopt clues
  • A busy price + tax has only seen small integers, so the engine makes fast code for Smis.
  • The same fast + gets "12", and the Smi guard fails.
  • Other code changes a property on a prototype, and V8 marks the fast code that relied on it.
  • Convert prices from a server with Number(value) once, before the hot code adds them up.
  • A property read made for one object layout gets an object with a different layout.
  • Time a helper with a warm-up and realistic values before rewriting it.
Try it yourself
0 of 6 correct

Put each card in its group: the engine is making a bet, a guard fails right now, a change elsewhere marks fast code out of date, or a fix you can make in your code.

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

Deopt loops

A deopt loop is when the same code goes round and round. The engine makes fast code for one kind of value. Soon another kind arrives, and the engine deopts. It collects new notes, makes fast code again, and the same thing happens. All that work is wasted. Engines count these rounds, and after a few they may wait longer or stop making fast code for that function.

One + that gets numbers and strings in turnPop out in the code editor (opens in a new tab)JavaScript
function addOne(value) {  return value + 1;} const values = [1, "1", 2, "2"];console.log(values.map(addOne).join("|"));

Line 2 has the +. Line 5 makes a list that switches between numbers and strings. Line 6 sends every item through addOne, then joins the results with |. It prints 2|11|3|21: 1 + 1 is 2, but "1" + 1 joins into "11".

A circle of three steps. Fast code meets another kind of value, so the check fails. That is a deopt, back to the slow path. The slow path tries fast code again. In the middle: the values 1, "1", 2, "2" keep switching.fast codecheck failsslow pathother valuedeopttry again[1, "1", 2, "2"]values switch
When one + keeps getting numbers and strings in turn, the engine can go round this circle again and again. After a few rounds, it may stop making fast code for that function.

Four calls are far too few for the engine to make fast code. But if a hot + kept switching like this thousands of times, the circle in the picture could happen. Your fix is usually simple: keep the values going into a hot operation the same kind, or split different kinds into different functions.

  • Convert data from a server into the right kind once, before it reaches a hot loop.
  • Don't send many different kinds of values or object layouts through one hot operation.
  • Move rare cases into their own function when that makes the code clearer and your timings show it matters.
  • Keep code easy to read. Don't twist code to silence a trace line on code that is not hot.

Seeing deopts in Node

Node can print V8's deopts if you start it with special flags (options typed after node). The examples below run only in Node, so they can't run in the browser editor. The flags they use:

  • --trace-deopt prints a line each time V8 deopts.
  • --trace-opt prints a line each time V8 makes fast code.
  • --allow-natives-syntax turns on V8's test functions that start with %.
  • --no-concurrent-recompilation makes V8 build fast code right away, so the output comes in a steady order.

The lesson's test runs these examples in Node and checks only facts that stay the same between runs: Node's version starts with v22., V8's starts with 12.4., an eager deopt line has a kind and a reason, and an out-of-date line mentions code dependencies.

Node only: make an eager deopt happenJavaScript
function add(a, b) {  return a + b;} %PrepareFunctionForOptimization(add);for (let i = 0; i < 100000; i += 1) {  add(i, i + 1);}%OptimizeFunctionOnNextCall(add);add(1, 2);add("x", "y");
  1. Line 5: %PrepareFunctionForOptimization tells V8 to start keeping notes for add.
  2. Lines 6 to 8 call add 100,000 times with small integers.
  3. Line 9: %OptimizeFunctionOnNextCall tells V8 to make fast code on the next call.
  4. Line 10 runs the fast code with 1 and 2. The guard passes.
  5. Line 11 sends the strings "x" and "y". The Smi guard fails, and V8 deopts.
What the eager deopt line looks likeShell
$ node --allow-natives-syntax --no-concurrent-recompilation --trace-deopt probe.js
[bailout (kind: deopt-eager, reason: not a Smi): begin. deoptimizing ...]

bailout means "left the fast code". kind: deopt-eager says it happened right at a guard. reason: not a Smi says which check failed. The ... stands for details that change from run to run.

Node only: make fast code go out of dateJavaScript
const proto = { taxRate: 0.05 };const menu = Object.create(proto);menu.subtotal = 100;function price(row) {  return row.subtotal * (1 + row.taxRate);}%PrepareFunctionForOptimization(price);for (let i = 0; i < 100000; i += 1) price(menu);%OptimizeFunctionOnNextCall(price);price(menu);proto.taxRate = 0.12;price(menu);
  1. Lines 1 to 3 make proto with a tax rate, and a menu that borrows from it. Object.create(proto) makes a new object whose prototype is proto.
  2. Lines 4 to 6: price reads row.taxRate, which it finds on the prototype.
  3. Lines 7 to 10 warm up price, make fast code, and run it once. V8 assumes the prototype's tax rate won't change.
  4. Line 11 changes proto.taxRate. V8 marks the fast price code as out of date.
  5. Line 12 calls price again, after the mark.
What the out-of-date line looks likeShell
$ node --allow-natives-syntax --trace-deopt probe.js
[marking dependent code ... for deoptimization, reason: code dependencies]

The clue is marking dependent code ... for deoptimization with reason: code dependencies: fast code that depended on the prototype was marked.

Node only: ask whether a function has fast codeJavaScript
function add(a, b) { return a + b; }%PrepareFunctionForOptimization(add);add(1, 2);%OptimizeFunctionOnNextCall(add);add(3, 4);const status = %GetOptimizationStatus(add);console.log("status is number", typeof status === "number");

Lines 2 to 5 prepare add, call it, ask for fast code, and call it again. Line 6 asks V8 for a status number with %GetOptimizationStatus. Line 7 only checks that it is a number and prints status is number true. The exact number changes between V8 versions, so the test doesn't rely on it.

What not to check in a test

Don't write tests that expect memory addresses, compile times, positions in the compiled code, exact status numbers, or whole trace lines. They change between computers and even small V8 updates. Check broad clues instead, such as kind: deopt-eager and that a reason is there.

Habits for real code

Most frontend developers don't need to chase every deopt. This knowledge helps when you have measured a slow, hot path: a parser, the code that adds up chart data, a function that formats every cell of a big table, or an animation loop. In that code, keep the values plain and predictable.

Read each row of the table as: "this habit means doing this, and it helps because of this."

Everyday habits that help fast code, without bending your code out of shape
HabitWhat to doWhy it helps
Convert data earlyTurn text from a server into numbers before the hot loop.The hot + keeps one job, adding, instead of switching between adding and joining text.
Keep object layouts steadyCreate properties in the same order, and don't delete properties from hot objects.Map checks keep passing, so property reads stay fast.
Measure before changing codeTime real user actions, or a test that warms up first and uses realistic values.A deopt line is a clue. It doesn't prove that users feel any slowness.
Use trace flags only in experimentsRun Node or Chrome with trace flags on your own computer, never in your app's code.The flags and % functions belong to V8 and can change in any version.
Turn text into numbers before adding them upPop out in the code editor (opens in a new tab)JavaScript
function toCents(value) {  return Number(value);}const inputs = ["100", "50", "25"];console.log(inputs.map(toCents).reduce((sum, value) => sum + value, 0));

Lines 1 to 3 make toCents, which turns a value into a number with Number(value). Line 4 is data from a server, where the prices arrive as text. Line 5 first turns every price into a number with toCents, then adds them up with reduce. So the + inside reduce only ever adds numbers. It prints 175. If speed matters, time the real page before and after the change.

Common mix-ups

  • "A deopt means my code is wrong." No. It means the engine's bet stopped fitting. Your code still gives the right answer.
  • "The notes go back to narrow after a tidy call." Usually not. Notes get wider, so the next fast code stays safe for every value seen.
  • "A watchpoint runs my JavaScript function." No. It is a note inside the engine. It marks fast code out of date and never runs your code.
  • "Every trace line must be fixed." No. Fix hot paths you have measured. Ignore lines from code that rarely runs.

The table below puts four terms side by side. Read each row as: "this term means this, and it does not mean that."

What each term means, and what it doesn't
TermWhat it meansWhat it doesn't mean
SpeculationThe engine's bet: fast code for the values it has seen, with guards and a way back.A change to what JavaScript means, or a guess that could give a wrong answer.
Type feedbackNotes about the kinds of values that reached each operation.A promise that future values will be the same kind.
DeoptimizationA safe step back to slower code that still gives the right answer.An error or a bug in your program.
Trace outputA clue from one version of one engine.A promise that stays the same everywhere, or text a test should match exactly.

Practice exercises

The first exercises ask what code prints. The later ones ask you to read a trace and fix real code. Try a guess first, then use the hints if you get stuck.

Exercise 1 · Warm-upPredict the first number

Read the code without running it. What is the first line it prints?

Starter codePop out in the code editor (opens in a new tab)JavaScript
function totalCents(price, tax) {
  return price + tax;
}

console.log(totalCents(100, 8));
console.log(totalCents(250, 20));

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

    Exercise 2 · Warm-upName the last state on the ladder

    Use the feedback ladder table. Which state does the code print at the end?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    let state = "None";
    function merge(previous, observed) {
      if (previous === "None") return observed;
      if (previous === observed) return previous;
      if (previous === "SignedSmall" && observed === "Number") return "Number";
      if (previous === "Number" && observed === "NumberOrOddball") return "NumberOrOddball";
      return "Any";
    }
    state = merge(state, "SignedSmall");
    state = merge(state, "Number");
    state = merge(state, "NumberOrOddball");
    console.log(state);

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

      Exercise 3 · PracticeFind the surprise from a string

      What does the second call print? Why would that surprise fast code made only for numbers?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function addFee(value) {
        return value + 1;
      }
      
      console.log(addFee(4));
      console.log(addFee("4"));

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

        Exercise 4 · PracticeConvert the data before the hot code

        Finish the function so that code which adds up the prices later gets numbers, not a mix of text and numbers. Then name the fix in a few words.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        function normalizePrice(value) {
          // return a number here
        }
        
        const raw = ["100", "50", "25"];

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

          Exercise 5 · PracticeRead the kind of deopt

          Look at the sample eager deopt line in "Seeing deopts in Node". Which part of it names the kind of deopt?

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

            Exercise 6 · ChallengeApply it to a real website

            Pick a big table, a chart, a parser, or an animation in an app. What would you check before changing code? If the values keep switching kinds, what is one safe fix?

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

              Quiz

              Speculation and deoptimization quiz · 8 questionsScore: first tries count
              1. Question 1 of 8What is speculative optimization?

                Choose an answer to see the explanation.

              2. Question 2 of 8What does this snippet print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                function addFee(value) {
                  return value + 1;
                }
                console.log(addFee(4));
                console.log(addFee("4"));

                Choose an answer to see the explanation.

              3. Question 3 of 8In this lesson's feedback ladder, which order do the states go in for a +?

                Choose an answer to see the explanation.

              4. Question 4 of 8What usually happens when a Smi check fails in fast code?

                Choose an answer to see the explanation.

              5. Question 5 of 8What does this lattice model print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                let state = "None";
                function merge(previous, observed) {
                  if (previous === "None") return observed;
                  if (previous === observed) return previous;
                  if (previous === "SignedSmall" && observed === "Number") return "Number";
                  return "Any";
                }
                state = merge(state, "SignedSmall");
                state = merge(state, "Number");
                state = merge(state, "String");
                console.log(state);

                Choose an answer to see the explanation.

              6. Question 6 of 8What does a watchpoint (V8 calls it a code dependency) do?

                Choose an answer to see the explanation.

              7. Question 7 of 8What is a deopt loop?

                Choose an answer to see the explanation.

              8. Question 8 of 8You see --trace-deopt output. What is the best next step for a real app?

                Choose an answer to see the explanation.

              Key takeaways

              • The engine makes fast code for the values it has seen, and puts guards in front to check every new value.
              • The notes for an operation only get wider. One new kind of value can move an operation up the ladder for good.
              • A failing guard causes an eager deopt, right away. A change elsewhere marks fast code out of date, and it is left later: a lazy deopt.
              • Use deopt traces to study hot paths you have measured. Keep the kinds of values and object layouts steady where it really helps.

              One-line summary: speculation makes the usual case fast, and deoptimization keeps the unusual case correct.

              Next, What optimizing compilers do shows the changes the compiler makes to your code once it trusts its bets, such as copying a small function into its caller (inlining) and working out sums ahead of time (constant folding).

              CompleteFrontend Clear concepts. Working examples.