This is V8 behaviour, from V8's source and tracing flags; it is not in ECMAScript. Observed on Node 24.19 (V8 13.6); wording and details change between versions.
Optimizing tiers (Maglev, TurboFan) compile from bytecode plus the feedback gathered while the function ran (pipeline). They assume the feedback stays true and insert guards that check it. A failed guard triggers a deoptimization ("deopt", "bailout"): V8 throws away the optimized frame, rebuilds the interpreter state, and continues in bytecode. The function can be marked hot again and recompiled with the new feedback.
V8's deoptimize-reason.h lists the reasons. The common ones come from broken assumptions:
- Shape:
wrong map, when an object with a different hidden class arrives (hidden classes). - Number representation:
not a Smi,overflow,lost precision,minus zero,division by zero(Smis). - Types:
not a Number,not a String,not a JavaScript object, and similar. - Array access:
out of bounds,hole,the array length changed. - Calls:
wrong call target. - No data:
Insufficient type feedback for call(and for binary operation, generic named access, and others), when optimized code reaches a path that never ran before the compile.
A deopt is a correctness mechanism, not an error. The cost is in a function that keeps being optimized, invalidated and optimized again, or in a path whose inputs never settle.
To see them, run Node with --trace-opt --trace-deopt.