advancedJIT

Why can a JIT sometimes outperform static compilation?

Whether the candidate understands that the JIT's advantage is *information*, not cleverness — and whether they immediately supply the other half of the ledger. A candidate who only says "it can specialize" has half an answer.

What a strong answer covers

  • Because it knows things a static compiler cannot. It has observed which types actually flow through a call site, which branches actually get taken, which methods are actually reached, and which loops actually run hot. A static compiler must be correct for every possible execution; a JIT can compile for the execution it is looking at and install a guard to catch the case where it was wrong.
  • That turns into concrete transformations: a monomorphic call site with an inline cache becomes a direct call and then an inlined body; a branch that has never been taken becomes an uncommon trap; a field access on an object whose shape has been stable becomes a fixed offset load. The specialization is only sound because the guard exists — when a guard fails, the JIT deoptimizes, reconstructing the interpreter state from a side table and resuming there.
  • It also has the final program. Devirtualization across a dynamically loaded plugin is available to a JIT and closed to a static compiler that never saw the plugin.
  • The other half: none of it is free. The program starts in an interpreter or a cheap tier and runs slowly until enough profile has accumulated — warmup. Compilation happens on the machine running the workload, competing with it for CPU and memory. Compiled code, profiles and metadata cost footprint. Performance is not reproducible run to run, and a pathological deoptimization loop can leave a method oscillating between tiers. For a short-lived process, a static build usually wins outright, which is why AOT and tiered warmup caches exist at all.
✓ Green flags
  • Frames the advantage as observed runtime facts, and names at least one specifically — receiver types, branch bias, hotness.
  • Explains guards and deoptimization as the mechanism that makes speculation sound.
  • Volunteers warmup and compilation overhead without being asked.
  • Notes that startup-dominated and short-lived workloads favour static compilation.
  • Mentions that a JIT sees dynamically loaded code that a static compiler never had.
✗ Red flags
  • "A JIT is always faster because it optimizes at runtime with more information." The information is real; the unconditional claim ignores warmup, memory and compile-time competition for the same CPU.
  • "The JIT compiles once when the method is first called." Tiered systems compile the same method several times at increasing effort, and may discard compilations entirely.
  • "Deoptimization means the JIT gave up and fell back permanently." It falls back for that activation, records why, and usually recompiles with the failed assumption removed.
  • "You can get the same result by turning on -O3 in a static compiler." -O3 cannot know the receiver type at a virtual call site in code it never saw.

Follow-up

A service shows p99 latency spikes for the first two minutes after deploy. Walk me from that symptom to the tiering behaviour, and then to two things you could change.

Implementation challenge

What to ask them to write or trace on a whiteboard.

Sketch the data structure a JIT needs so it can deoptimize: what has to be recorded at compile time for the runtime to rebuild an interpreter frame at an arbitrary safepoint?

The lessons behind it