The JavaScript engine

Optimization and deoptimization

Understand why stable runtime behavior can help optimized code and why an engine may discard an incorrect optimization.

JavaScript is dynamic. The same function can receive numbers on one call, strings on the next, and objects with different properties after that. The engine must handle all of it correctly.

But it can also bet. When it watches a function run many times with the same kinds of values, it generates faster code specialized for those values.

Take this function:

function total(price, quantity) {
  return price * quantity
}

If a hot call site keeps passing two numbers, the engine compiles a fast path that assumes numbers. The multiplication becomes a single machine instruction. That fast path is protected by a guard, a quick check that the assumption still holds.

Now a later call passes a string:

total('19.90', 2)

The guard fails. The engine throws away the optimized version and falls back to the general, slower code, or compiles a new version that handles both cases. This step is called deoptimization.

Deoptimization is not a bug

Deoptimization is a correctness mechanism. It’s the engine keeping its promise that your code behaves the same no matter what, while trying to be fast when it can.

The wrong conclusion is that you must rewrite every function that sees mixed types. Most functions never run often enough for any of this to matter. And when a page is slow, the cause is almost always the network, the rendering pipeline, or an algorithm doing too much work. Engine internals come last.

How I investigate slow JavaScript

I use this order, and I stop as soon as I find the problem:

  1. Record the real interaction in the Performance panel.
  2. Find a function with a meaningful total time.
  3. Check whether the algorithm repeats avoidable work.
  4. Only then look at engine-specific optimization behavior.

Step 3 solves most cases. A function called 10,000 times in a loop is slow because of the loop, not because of deoptimization.

Be careful with microbenchmarks

A microbenchmark warms up one tiny function under artificial conditions. The engine optimizes it perfectly, and the numbers look great. Then real code calls the same function with real, varied data, and the numbers mean nothing.

Test the complete user action instead. Change the code, record the same action again, and confirm the recording improved. A faster isolated loop that doesn’t change the recording is not an optimization. It’s noise.

Lesson completed