cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Type feedback & feedback vectors

Learn how JavaScript engines collect runtime type feedback in vectors and use warm-up evidence to specialize hot code safely.

By the end, you can
  • 01
    Explain feedback collectionDescribe how an engine records the values actually seen by bytecode sites instead of relying on static JavaScript types.
  • 02
    Read slots and statesConnect bytecode operands such as [0] with FeedbackVector slots for operators, property access, calls, loops, and literals.
  • 03
    Warm up code honestlyPredict why cold runs are slow, why one surprising value widens feedback, and how to benchmark hot code without fooling yourself.

Engines learn from the values your code actually uses

JavaScript has dynamic values, not static runtime type declarations. An optimizing compiler therefore cannot safely assume that a + b always means small-integer addition just because the first call used numbers. It watches the program run, records what each operation site saw, and later speculates from that evidence.

Definition

Type feedback is runtime evidence stored by a JavaScript engine for a particular bytecode operation: the kinds of operands, targets, object maps, or allocation patterns that site has actually seen.

This lesson sits between tiered compilation and inline caches. We focus on where the evidence lives and how it changes. Later lessons on speculative optimization and deoptimization explain how optimizing compilers consume this feedback and recover when a guess is wrong.

Real-life analogyA barista remembers regular orders

Imagine a cafe where each station writes down what it sees. If the morning rush at the espresso station is always double shots, the barista prepares that station for espresso. If one customer asks for a smoothie at the same station, the old shortcut is no longer safe. Type feedback works the same way for hot JavaScript operations.

In real life: The barista watches real customers
In JavaScript: The interpreter observes actual calls
In real life: One drink station has a notebook
In JavaScript: One bytecode site has a feedback slot
In real life: Usual espresso orders can be prepared early
In JavaScript: A stable type pattern can get a specialized fast path
In real life: A surprising smoothie order changes the plan
In JavaScript: A new type widens the feedback and may deoptimize

Where the analogy stops: A barista can use judgment and memory across the whole cafe. Engine feedback is mechanical, site-local, and must preserve exact JavaScript semantics even when the pattern changes.

Keep the prerequisites nearby: data types, numbers in depth, type coercion, bytecode and interpreters, and measuring performance.

Collecting types at runtime

INTERPRETER EVIDENCE

Feedback starts cold. The interpreter runs bytecode and records what each interesting operation observes. In V8, Ignition collects this information while executing bytecode. TurboFan, Maglev, and inline-cache machinery can later use it, but the first job is just to gather honest evidence from real values.

A toy feedback collectorPop out in the code editor (opens in a new tab)JavaScript
const slot = createSlot("add slot #0"); function createSlot(id) {  return { id, state: "None", count: 0 };} function classify(left, right) {  if (Number.isInteger(left) && Number.isInteger(right)) return "SignedSmall";  if (typeof left === "number" && typeof right === "number") return "Number";  if (typeof left === "string" && typeof right === "string") return "String";  return "Any";} function merge(previous, observed) {  if (previous === "None" || previous === observed) return observed;  if ((previous === "SignedSmall" && observed === "Number") || (previous === "Number" && observed === "SignedSmall")) return "Number";  return "Any";} function specialize(state) {  return {    SignedSmall: "int add fast path",    Number: "number add fast path",    String: "string concatenate fast path",    Any: "generic add path",  }[state] ?? "collecting in the interpreter";} function callAdd(slot, left, right) {  const observed = classify(left, right);  slot.state = merge(slot.state, observed);  slot.count += 1;  const result = left + right;  return {    result,    summary: slot.state + " -> " + specialize(slot.state),  };} function callOptimizedAdd(plan, left, right) {  return left + right;} console.log(callAdd(slot, 1, 2).summary);console.log(callAdd(slot, 1.5, 2.25).summary);console.log(callAdd(slot, "1", "2").summary);

Line 1 creates one empty slot. Lines 7 through 12 classify a single + call. Lines 14 through 18 model the important rule: feedback gets more general, not more specific. The first log is SignedSmall -> int add fast path. The second log widens to Number. The string call makes this same site generic because JavaScript + can mean concatenation too.

Feedback walkthrough: integers widen to numbers
Step 0 of 5Ready
Your turn: follow the blue line

Replay a toy feedback slot as it learns from real calls. The state moves from none, to small integers, to general numbers.

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
callAdd(slot, 1, 2);callAdd(slot, 3, 4);callAdd(slot, 1.5, 2.25);
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.

Notice the site-local part. A different + expression elsewhere in the function would have its own slot and could remain string-only or integer-only. Engines optimize sites, not the entire operator globally.

Feedback vectors and slots

V8 SHAPE

In V8, the metadata describing which slots a function needs belongs to the shared function information. The runtime feedback itself lives in a FeedbackVector. A modern V8 JSFunction points through a FeedbackCell, which lets V8 delay allocating the full vector until the function becomes hot enough. V8 calls this lazy feedback allocation.

Verified V8 model

V8's public function-architecture document describes JSFunction, FeedbackCell, and FeedbackVector. The V8 Lite blog explains why feedback vectors are allocated lazily after enough bytecode executes, saving memory for functions that never become hot.

The bytecode operand points to a slotShell
$ node --print-bytecode --print-bytecode-filter=add add.js[generated bytecode for function: add]Parameter count 3   19 S> ... @    0 : 0b 04             Ldar a1   28 E> ... @    2 : 3b 03 00          Add a0, [0]   32 S> ... @    5 : ae                Return

The [0] in Add a0, [0] is not an array literal. It is the feedback slot operand for that add bytecode. When the function runs enough, V8 can allocate a feedback vector whose slot 0 records the binary operation hint.

A simplified add lattice:

  1. None
  2. SignedSmall
  3. Number
  4. NumberOrOddball
  5. Any

Strings and BigInts form separate branches in real V8 hints: String, StringOrStringWrapper, BigInt64, and BigInt also exist before a site reaches Any.

Feedback for calls, properties, operators, loops, and literals

SLOT KINDS

V8's feedback slot kinds include calls, property loads, keyed loads, stores, binary operations, comparisons, literals, for...in, instanceof, typeof, object cloning, string-add-and-internalize, and jump-loop feedback. You do not need to memorize the list, but it explains why feedback is richer than “the type of a variable.”

Feedback slots are specialized by operation
Slot kindWhat it recordsWhy it matters
BinaryOp / CompareOpOperand hints such as SignedSmall, Number, NumberOrOddball, String, BigInt64, BigInt, or Any.Arithmetic, comparisons, and the + operator's numeric/string split.
LoadProperty / Store / keyed accessMaps or shapes plus handlers for how to load or store the property.Feeds inline caches; the inline-caches lesson goes deep on IC states.
CallThe target function and a call count-like payload in the slot.Helps decide whether a hot call site is stable enough to inline.
ForIn / InstanceOfEnumeration-cache hints or constructor/prototype feedback.Speeds repeated loops and membership checks when shapes stay predictable.
Literal / allocation siteA boilerplate or allocation site for arrays, objects, and literals.Lets engines reuse literal structure and track allocation behavior.
Property feedback starts from maps or shapesPop out in the code editor (opens in a new tab)JavaScript
function readName(user) {  return user.name;} console.log(readName({ name: "Ada", role: "admin" }));console.log(readName({ name: "Grace", role: "engineer" }));

A property load such as user.name records the object maps or shapes it has seen. That feeds the inline-cache states you will study in Inline caches. This lesson names the evidence; the next one names the monomorphic, polymorphic, and megamorphic states in depth.

Call feedback can name a targetPop out in the code editor (opens in a new tab)JavaScript
function apply(fn, value) {  return fn(value);} function inc(value) {  return value + 1;} let calls = 0;for (const value of [1, 2, 3]) {  calls += 1;  apply(inc, value);} console.log("target: " + inc.name);console.log("calls: " + calls);

A call slot can record a stable target function and call information. Optimizers use that evidence when deciding whether a call is worth inlining. Literal feedback points at allocation sites or boilerplates, so repeated object and array literals do not start from scratch every time.

Why the first runs are slow, and what warm-up means

COLD VS HOT

A cold function pays setup costs: parsing or compiling bytecode, running in the interpreter, allocating feedback structures lazily, filling slots, and waiting until tiering heuristics decide the function is hot enough. The first few runs are often the least representative runs in a benchmark.

Warm up with representative values before measuringPop out in the code editor (opens in a new tab)JavaScript
function add(a, b) {  return a + b;} for (let i = 0; i < 10000; i += 1) {  add(i, i + 1);} console.log("warmed with 10000 number calls");console.log(add(1, 2));

The loop does not make add faster by magic. It gives the engine a chance to collect number feedback and, if the function stays hot, compile a plan for the values it actually sees. Warm-up only helps if the warm-up values match the measured workload.

Speculative guard walkthrough: fast path then fallback
Step 0 of 6Ready
Your turn: follow the blue line

Replay a toy optimizer: feedback creates a guarded fast path, and a surprising type falls back without changing JavaScript's result.

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
callAdd(slot, 1, 2);const plan = specialize(slot.state);callOptimizedAdd(plan, 3, 4);callOptimizedAdd(plan, "3", "4");
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.

If a benchmark warms with numbers and measures strings, it measured a type transition. Sometimes that is exactly the scenario you care about. Most of the time it is accidental noise. Use the benchmarking habits from Measuring performance: warm up, keep results used, run many samples, and report variance or medians.

Real V8 evidence from Node 22.23.1

V8 12.4

The lesson tests run child Node processes with Node 22.23.1 and V8 12.4. They assert stable substrings only, because addresses, counts, and exact tiering details can vary. The important facts are visible in release builds: bytecode operands, feedback vectors, slot kinds, lazy allocation messages, and deopt reasons.

Native-syntax probe for a feedback vectorJavaScript
function add(a, b) {  return a + b;} for (let i = 0; i < 30; i += 1) {  add(i, i + 1);} %DebugPrint(add);
Stable output excerpt: Node 22.23.1 (V8 12.4)Text
Node 22.23.1 (V8 12.4)- feedback vector: ... [FeedbackVector]- invocation count: 22- slot #0 BinaryOp BinaryOp:SignedSmall { ... }
Lazy feedback allocation excerptText
Node 22.23.1 (V8 12.4)NEVER: feedback metadata is not available in SFIONCE: No feedback vector, but we have a closure feedback cell arrayMANY: slot #0 BinaryOp BinaryOp:SignedSmall
Trace optimization and deoptimizationJavaScript
function add(a, b) {  return a + b;} for (let i = 0; i < 100000; i += 1) {  add(i, i + 1);} %OptimizeFunctionOnNextCall(add);add(1, 2);add("x", "y");
Stable trace excerpt: Node 22.23.1 (V8 12.4)Text
$ node --allow-natives-syntax --no-concurrent-recompilation --trace-opt --trace-deopt probe.js   # Node 22.23.1 (V8 12.4)[marking ... reason: hot and stable][completed compiling ...][bailout (kind: deopt-eager, reason: not a Smi): begin. deoptimizing ...]

In this V8 build, an integer-only add site prints BinaryOp:SignedSmall. Adding a double widens it to BinaryOp:Number. Boolean or null operands can produce NumberOrOddball. String-only addition can be String, small BigInts can be BigInt64, larger BigInts can be BigInt, and mixed meanings reach Any.

Experiment: make one add site specialize or go generic

PLAYGROUND

Use the buttons to feed one toy slot. Start with number pairs, then try strings or BigInts. Watch the current slot state, the specialization it would suggest, and whether the selected call would pass the current guard.

Play with one add feedback slot
Toy feedback collector used by the playgroundJavaScript
const slot = createSlot("add slot #0");callAdd(slot, 1, 2);        // SignedSmall feedbackcallAdd(slot, 1.5, 2.25);   // widens to NumbercallAdd(slot, "1", "2");    // jumps to Any for this site
Slot state0 calls
current stateNone
would specialize ascollecting in the interpreter
selected guardwould pass

No calls yet. Start with a number pair, then try a string pair.

Try it yourself

Pick calls to warm this one add site. The slot begins empty, then moves upward as it sees broader types.

This is a teaching model, not a browser engine inspector. It records the calls you choose and shows the kind of specialization an optimizer could try.
Stays fast or goes generic?
  • add(1, 2) then add(3, 4) at the same bytecode site.
  • add(1, 2) then add(1.5, 2.25).
  • add(1, 2) then add(true, false).
  • add(1, 2) then add("1", "2").
  • A fresh site only ever runs add("a", "b").
  • add({ valueOf() { return 1; } }, 2) after number warm-up.
Try it yourself
0 of 6 correct

Sort each call pattern by what happens to one feedback slot in the lesson's toy lattice.

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

What this means for production code

READABILITY FIRST

Type feedback is not a reason to write strange code. Most application code should be clear first. The practical rule is narrower: on hot paths you have measured, keep value types and object shapes reasonably consistent. Avoid using one function sometimes as a number path, sometimes as a string path, and sometimes as an object-conversion path if it shows up in a profile.

  • Initialize the same object fields in the same order when you create many similar objects.
  • Keep hot arithmetic sites numeric if they are meant to be numeric; convert at the edge instead of inside the loop.
  • Do not trust a microbenchmark until warm-up values match production values.
  • Prefer readable code unless profiling shows the site is hot and unstable.
Link, do not duplicate

Tier changes belong to Tiered compilation. IC state machines belong to Inline caches. Speculative guards and deoptimization loops come later. Here, the core idea is the evidence those topics depend on.

Common misconceptions

“Feedback is the same as TypeScript types.”

TypeScript is checked before runtime and erased. Engine feedback is collected while JavaScript executes.

“One function has one type.”

Feedback is attached to operation sites. Two + expressions in one function can have different states.

“Warm-up always makes benchmarks fair.”

Warm-up only helps when it uses representative values and the measured result is still used.

“A generic site is a bug.”

Generic feedback is sometimes the correct model for flexible code. Optimize only measured hot paths.

“Native syntax belongs in app code.”

%DebugPrint and optimization intrinsics are diagnostics for controlled experiments, not production APIs.

Ideas that are easy to confuse
IdeaWhat it meansCommon trap
Static typeA guarantee before the program runs.JavaScript engines mostly collect feedback while the program runs.
Type feedbackRuntime evidence at a specific operation site.It can be too narrow when the site has not warmed up yet.
Inline cacheThe machinery that caches property/call behavior at a site.IC state details are the next lesson; this lesson focuses on the evidence.
Speculative optimizationCompiled code that assumes future values look like past values.Covered later with deoptimization; here we only show the feedback it consumes.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upName the widened numeric state

What state follows SignedSmall when the same add site sees a double?

Starter codePop out in the code editor (opens in a new tab)JavaScript
SignedSmall -> ?
// after one call with 1.5 and 2.25

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

    Exercise 2 · PracticePredict the string surprise

    What does the toy slot become after a number-warmed + site sees two strings?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    add(1, 2);
    add("1", "2");

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

      Exercise 3 · PracticeRead a bytecode operand

      What does [0] name in the bytecode line?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      Add a0, [0]

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

        Exercise 4 · PracticeExplain benchmark warm-up

        Why should a microbenchmark run representative warm-up iterations before timing?

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

          Exercise 5 · ChallengeState the production rule

          What should stay reasonably consistent on a measured hot path?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          // Fill in the rule:
          // On measured hot paths, keep ____ reasonably consistent, but readability first.

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

            Quiz: check your understanding

            8 QUESTIONS

            Answer by locating the operation site, naming the feedback it saw, and deciding whether the next plan can stay specific.

            Lesson quiz · 8 questionsScore: first tries count
            1. Question 1 of 8What is type feedback?

              Choose an answer to see the explanation.

            2. Question 2 of 8What does this toy feedback snippet print last?

              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" || previous === observed) return observed;
                if (previous === "SignedSmall" && observed === "Number") return "Number";
                return "Any";
              }
              state = merge(state, "SignedSmall");
              state = merge(state, "Number");
              console.log(state);

              Choose an answer to see the explanation.

            3. Question 3 of 8In V8 bytecode such as Add a0, [0], what is [0]?

              Choose an answer to see the explanation.

            4. Question 4 of 8Which call sequence pushes the same + site to a generic toy state?

              Choose an answer to see the explanation.

            5. Question 5 of 8What does this warm-up example print first?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function add(a, b) { return a + b; }
              for (let i = 0; i < 3; i += 1) add(i, i + 1);
              console.log("warmed with number calls");
              console.log(add(1, 2));

              Choose an answer to see the explanation.

            6. Question 6 of 8Why can a hot call site become a candidate for inlining?

              Choose an answer to see the explanation.

            7. Question 7 of 8Which benchmark habit follows from type feedback?

              Choose an answer to see the explanation.

            8. Question 8 of 8What should production code do with this knowledge?

              Choose an answer to see the explanation.

            Key takeaways

            • JavaScript engines collect feedback because source text alone does not reveal stable runtime types.
            • In V8, bytecode operands point to feedback slots in a lazily allocated FeedbackVector.
            • Binary and compare hints include states such as SignedSmall, Number, String, BigInt64, and Any.
            • Property and call feedback feed inline caches and inlining decisions; literal feedback can use allocation sites.
            • First runs are cold because they interpret, allocate feedback lazily, collect evidence, and wait for tier-up.

            Remember the one-liner.
            Feedback vectors are the engine's memory of what each hot bytecode site actually saw.

            Up next: Inline caches, where those property and call sites become monomorphic, polymorphic, or megamorphic.

            CompleteFrontend Clear concepts. Working examples.