Interpreter vs Compiler

On this page 12

Part I — Foundations · Interview reference

Execution model literacy: how source becomes machine work, where time goes (parse, compile, warmup, runtime), and how that drives language/runtime choices in production systems.


Definitions

ModelDefinition
Compiler (AOT)Translates source (or IR) to machine code ahead of time → native binary
InterpreterExecutes source or bytecode instruction-by-instruction without producing a standalone native binary for the whole program
Bytecode + VMHybrid: compile source → portable bytecode; VM interprets and/or JITs
JITCompiles hot paths to native code at runtime using profiling data
AOT (modern)e.g. Graal native-image, .NET Native, Go — compile ahead, often with different optimization limits than JIT

Most production languages are hybrids, not pure textbook interpreter or compiler.


Comparison

DimensionClassic interpreterAOT nativeBytecode + JIT (JVM/CLR/V8)
StartupFast to begin executingFast once binary loadedSlower (classload + warmup)
Peak throughputLowerHighOften highest (PGO-like)
PortabilityHighPer-arch binariesHigh bytecode; JIT per-arch
Observability / rewriteEasy eval / REPLHarderAgent / instrumentation rich
Optimization infoLimitedStaticSpeculative + deoptimize
Binary size / depsSmall runtimeLarger or staticLarge runtime (JVM)

Pipeline mental model

Source → Parse/AST → (Optional bytecode/IR) → Optimize → Execute
                         ↓
              Interpreter loop and/or JIT tiers
                         ↓
              Deoptimization / recompilation (JIT systems)

Key JIT ideas

  • Interpretation / C1 first for quick startup
  • Hotspot detection → higher optimization tiers (C2 / Graal)
  • Speculative opts (type feedback, inlining) + deopt if assumptions fail
  • Inline caches for dynamic dispatch (JS, and polymorphic call sites on JVM)

Language examples (map correctly in interviews)

LanguageModel (simplified)
C/C++/Rust/GoAOT to native (Go has GC runtime)
Java / Kotlin / Scalajavac → bytecode → JVM interpret/JIT (AOT optional)
C#IL → CLR JIT (AOT options exist)
JavaScriptParse → bytecode → JIT tiers (V8 Ignition/TurboFan etc.)
Python (CPython)Bytecode interpreted; GIL-bound; alternate runtimes (PyPy JIT)
SQLQuery compiled/planned to execution operators (not a general language VM story, but “compiler” vocabulary appears)

Java under the hood

.java → javac → .class bytecode → class load/verify
              → template interpreter (cold)
              → C1 (client) JIT → C2 / Graal (server) on hot methods
              → deoptimize if speculative assumptions fail
PieceRole
BytecodeStack-based ops; portable; verified before run
Tiered compilationInterpret / C1 first for startup; C2 for peak throughput
ProfilingType feedback, branch frequencies drive inlining & speculative opts
invokedynamicLambdas, string concat, dynamic languages — call-site linkage
Graal native-imageClosed-world AOT: fast start, smaller footprint; limited dynamic features vs JIT peak
WarmupFirst requests pay classload + JIT; measure after soak (JMH, realistic traffic)

“Is Java compiled or interpreted?” → both: AOT to bytecode, then interpret and/or JIT (optional AOT native). Details in JVM.

Production case study (high volume)

Context: Payments API on HotSpot (~tens of K RPS per cluster) vs a latency-sensitive Lambda/Fargate sidecar compiled with Graal native-image for auth policy checks. Why seniors care: Post-deploy p99 is dominated by warmup (classload + C2), not “Java is slow”; native-image trades peak throughput / peak opts for cold-start and footprint; wrong choice fails SLOs differently. Failure / symptom: Canary looks bad for 5–15 minutes then fine; Lambda p99 timeouts on cold paths; megamorphic call sites after a plugin refactor never reach C2 peak. Resolution: Soak before declaring regression; gradual traffic / pre-warm; JMH + production mirrors; reserve AOT for cold-start budgets; keep long-lived services on JIT unless ops constraints win. Seen at / similar to: Shopify/Netflix JVM warmup discussions; AWS Lambda SnapStart / Graal; Twitter historical JVM tuning blogs.


Tradeoffs seniors must articulate

Choose AOT / native when

  • Cold start and predictable latency matter (CLI, serverless tight budgets, sidecars)
  • Deploy size / no JVM ops preferred
  • Hard real-time-ish constraints (with care)

Choose JIT VM when

  • Long-running services where warmup amortizes
  • Need rich tooling, GC, dynamic loading, hotswap/agents
  • Peak throughput and adaptive opts matter

Choose interpreted / dynamic when

  • Gluing, scripting, data science iteration speed
  • Accept throughput and packaging tradeoffs (or use specialized runtimes)

Performance & operations angles

  • Warmup: JIT systems show rising performance after load; benchmarks must account for this (JMH, realistic soak)
  • Deoptimization storms: changing shapes / megamorphic call sites
  • Debug vs release: assertions, bounds checks, inlining differences
  • Polyglot / FFI: crossing runtimes kills inlining and adds copy costs

Production case study (high volume)

Context: Ad-tech JVM service embeds a scripting/rules engine and calls into native crypto via JNI for every bid request (hundreds of K RPS). Why seniors care: Deoptimization storms and JNI transitions destroy inlining; “faster native” can raise p99; profile-guided reality beats theory. Failure / symptom: Code cache churn; JFR shows deopt; CPU in JNI transition; throughput drop after adding “flexible” dynamic proxies on the hot path. Resolution: Stabilize call shapes; move rare dynamic paths off the bid hot path; prefer pure-Java crypto where competitive; track deopt counts and code-cache occupancy. Seen at / similar to: High-QPS ad exchanges; Android ART/JIT history as analogy; V8 megamorphic IC stories applied to JVM polymorphic sites.


What interviewers probe

  1. “Is Java compiled or interpreted?” — require the layered answer (bytecode + JIT + possible AOT).
  2. JIT vs AOT tradeoffs for a microservice vs a CLI tool.
  3. Why PyPy or V8 can beat naive interpreters — hot paths + type specialization.
  4. Warmup and tail latency after deploy / scale-out.
  5. How reflection / dynamic proxies affect JIT (JVM link).
  6. Deopt / code-cache symptoms under production traffic shapes.

Senior-level expectation: Accurate hybrid model; connect runtime choice to SLOs (startup, p99, memory, ops skillset).


Pitfalls

  • Calling the JVM “just an interpreter”
  • Microbenchmarks without warmup or with dead-code elimination artifacts
  • Assuming AOT always faster than JIT peak
  • Ignoring binary portability and CVE patch story (static Go vs shared libc, JVM patch cadence)
  • Declaring a deploy “regressed” during JIT warmup windows
  • Putting megamorphic plugins on the highest-QPS path without measuring deopts

Cross-references

Interview reference — explanation quality and judgment, not syntax memorization.