Search

Compiled vs Interpreted vs JIT: Why Languages Run Differently

The short answer

Quick answer: There are three broad ways to run a program. An ahead-of-time (AOT) compiler translates the whole program into machine code before it runs, as with C, C++, Rust and Go. An interpreter reads the program and carries out its instructions one at a time, as with classic Python or Ruby. A just-in-time (JIT) compiler starts by interpreting, watches which parts of the code run most often, and compiles those hot parts into machine code while the program is running, as with JavaScript, Java and C#. "Compiled" and "interpreted" are properties of an implementation, not of a language.

Ahead-of-time compiledInterpretedJIT compiled
When translation happensBefore runningDuring running, step by stepDuring running, for hot code
Start-upFastFastFast start, slow warm-up
Peak speedHighLowestHigh after warm-up
Memory useLowLow to mediumHigher
What you shipA binary per platformSource or bytecode plus a runtimeSource or bytecode plus a runtime
ExamplesC, C++, Rust, GoCPython, Ruby (MRI), BashJavaScript (V8), Java (HotSpot), C# (.NET)

Ahead-of-time compilation

An AOT compiler does all the work up front: parse, check, optimise, generate machine code, link. The details are in how compilers work. The result is a file the operating system can load and run directly.

Strengths

  • The program starts at full speed.
  • No runtime compiler has to be shipped or kept in memory.
  • Many errors are caught before the program ever runs.

Weaknesses

  • You need a separate build for each processor and operating system.
  • The edit, compile, run loop is slower.
  • The compiler has to optimise without knowing how the program will actually be used.

Interpretation

A pure interpreter walks through the program and performs each operation as it reaches it. The simplest kind walks the syntax tree directly. Most real interpreters do something smarter: they first compile the source into bytecode, a compact set of instructions for an imaginary machine, and then run a loop that executes one bytecode instruction after another.

CPython works this way. You can see the bytecode yourself:

import dis

def area(w, h):
    return w * (h + 2)

dis.dis(area)

So even "interpreted" Python has a compile step. It just compiles to bytecode, not machine code.

Why interpreting is slower. For every bytecode instruction, the interpreter must fetch it, decode it, jump to the code that handles it, and often check the types of the values involved. One line of source can cost dozens of real machine instructions of overhead. In dynamically typed languages, a + b might mean integer addition, floating-point addition, string concatenation or a user-defined method, and the interpreter must work out which every single time.

Strengths: portability, instant start-up, easy debugging and interactive use (the REPL).

Just-in-time compilation

A JIT compiler tries to get the best of both. The usual design has tiers:

  1. Start in an interpreter. Code begins running immediately, with no compile delay.
  2. Profile. The runtime counts how often each function and loop runs, and records what types of values actually show up.
  3. Compile hot code. Functions that run often are compiled to machine code, using the profile to specialise it. If a + b has only ever seen integers, the JIT emits a plain integer add guarded by a quick check.
  4. Deoptimise if wrong. If a guard fails, say a string appears where integers always appeared, the runtime throws away the specialised code and falls back to the interpreter.

Google's V8 engine, used in Chrome and Node.js, has an interpreter and several compiler tiers, with TurboFan as its optimising compiler. Java's HotSpot virtual machine follows the same pattern.

The key advantage: a JIT knows things an AOT compiler can only guess. It sees the real types, the real branch outcomes and the real call targets, so it can optimise for what the program actually does.

The costs

  • Warm-up. Code is slow until it gets hot and compiled. This hurts short-lived programs and serverless functions.
  • Memory. The compiler, the profiles and the generated code all take space.
  • Unpredictability. A deoptimisation can cause a sudden slowdown.

The lines are blurry

Real implementations mix approaches freely:

  • Java is compiled ahead of time to bytecode, then interpreted, then JIT-compiled. It can also be compiled fully ahead of time to a native binary with tools such as GraalVM Native Image.
  • Python has several implementations. CPython is a bytecode interpreter and recent versions have begun adding an experimental JIT. PyPy is a JIT-compiled implementation of the same language.
  • JavaScript engines sometimes cache compiled code between page loads.
  • Android combines installation-time compilation with JIT compilation and profile data.
  • WebAssembly is a compact binary format designed to be compiled quickly by the browser.

This is why "Python is an interpreted language" is a loose statement. The language is a specification; whether it is interpreted or compiled depends on which implementation you run.

Which should you choose?

If you needLean towards
Predictable performance and low memory useAOT-compiled languages
Fast start-up for short tasks and command-line toolsAOT-compiled, or interpreted for small scripts
Rapid iteration and scriptingInterpreted languages
High throughput in long-running serversJIT-based runtimes
One artefact that runs anywhere a runtime existsBytecode plus JIT

In practice, the choice of language is usually driven by ecosystem and team knowledge, and performance problems are solved by optimising the few hot spots. Many "slow" languages lean on libraries written in C for the heavy work. Most interpreted and JIT-based runtimes also manage memory for you; see how garbage collection works.

Frequently asked questions

Is Python interpreted or compiled?

Both. CPython compiles source to bytecode, then interprets the bytecode. It does not compile to machine code ahead of time.

Why is a JIT sometimes faster than ahead-of-time compilation?

Because it can use information only available at run time, such as actual types and frequently taken branches, to specialise the code.

What is bytecode?

A compact, platform-independent instruction set for a virtual machine. It is easier to interpret than source text and easier to ship than machine code for every platform.

What does "warm-up" mean?

The period after start-up during which a JIT-based program is still interpreting and compiling. Performance improves as hot code gets compiled.

Conclusion

Compiled, interpreted and JIT are three answers to one question: when should translation to machine code happen? Before the program runs, never, or while it runs. Each trades start-up time, peak speed, memory and convenience differently, and most modern language runtimes combine more than one.

Related articles

Sources and further reading

Usama Muneer

Usama Muneer

Coder, Blogger, Tech Speaker & Web Technologies Enthusiast. Passionate about working on open-source Programming languages & Tools while utilizing my Product Development skills.

Your experience on this site will be improved by allowing cookies Cookie Policy