Compiled vs Interpreted vs JIT Languages: How Code Actually Runs

Trace one program from source to CPU through ahead-of-time compilation, bytecode interpretation and JIT compilation. Then compare their costs without relying on labels.

KnowledgeGate Team

Exam prep & CS education

Updated 25 Aug 20265 min read

Labels such as "C is compiled", "Python is interpreted" and "Java uses JIT" sound like three exclusive boxes. Real implementations can perform several translations before the CPU executes anything, which makes startup, portability and steady-state speed harder to reason about. The broader Programming Languages map gives the surrounding concepts. One small program answers four questions: what artifact exists, when translation happens, what executes each instruction, and whether runtime evidence can trigger new machine code.

Stop classifying the language; trace the implementation

The terms describe operations and timing. Ahead-of-time (AOT) compilation translates before the run. Interpretation executes a representation through an interpreter loop. Bytecode is an intermediate format, not machine code. Just-in-time (JIT) compilation translates selected code during execution, possibly using observed information.

Use this checklist:

  1. What is the starting artifact: source, bytecode or a native executable?

  2. When does translation happen: before launch or while running?

  3. What executes next: the physical CPU or a virtual machine or interpreter?

  4. Can a later stage replace interpreted work with generated native code?

"Compiled" and "interpreted" describe implementation paths. One language can have different implementations, and one run can include both operations. File extensions and syntax do not determine the complete path.

One program and one result for every execution path

Keep the program's meaning fixed while changing its execution path:

function sumEven(values):
    total = 0
    for x in values:
        if x % 2 == 0:
            total = total + x
    return total

input = [3, 8, 5, 12]

Start with total = 0. Inspect 3: it is odd, so the total stays 0. Inspect 8: the total becomes 8. Inspect 5: it stays 8. Inspect 12: it becomes 20. Return 20. After each element, the invariant is: total equals the sum of even values seen so far.

Every valid execution path must return 20. The compilation strategy changes when and how instructions are produced, not what this program is supposed to compute.

Ahead-of-time compilation creates native code before the run

C can follow this representative path: sum_even.c -> compiler and linker -> native executable -> operating-system loader -> CPU instructions. Preprocessing and linking may be separate, but native code exists before this run starts.

The compiler translates the loop and test without knowing the later input [3, 8, 5, 12]. At runtime, its machine instructions return 20.

A native executable can avoid interpreter dispatch, but this does not guarantee speed. Optimisation, processor, input and behaviour still matter. Portability usually requires a compatible target build.

Bytecode interpretation combines compilation and interpretation

CPython is one representative implementation. It parses Python source and compiles it to an internal bytecode or code representation. Its virtual machine evaluates those instructions, while the CPU executes the interpreter's native instructions. The path contains compilation and interpretation.

Here, bytecode represents initialising total, iterating over four items, testing x % 2 == 0, adding 8 and 12, then returning 20. Other Python implementations can choose different engines, so version-sensitive opcode names would distract.

Bytecode targets a virtual machine and normally needs a matching runtime. Native code targets a physical instruction set.

JIT compilation can specialise frequently executed work

A representative Java interpreter-plus-JIT path is: SumEven.java -> Java compiler -> JVM bytecode -> JVM loads and begins executing -> frequently executed method may be compiled to native code -> later calls can use that native version. JVMs and runs can differ.

Call sumEven([3, 8, 5, 12]) exactly 100,000 times and add each result to grandTotal. Each call returns 20, so grandTotal = 100,000 x 20 = 2,000,000. Repetition makes the method a plausible optimisation candidate, but it promises no JIT threshold.

The runtime spends time and memory compiling selected code, aiming to recover that cost later. Observations can support specialised native code, but speculation may need guards or a fallback. The Java track explores this execution model.

Three pipelines running sumEven([3, 8, 5, 12]) = 20: AOT native code, CPython bytecode interpretation, and Java bytecode with JIT.

Startup, throughput and portability depend on the boundary

Use T_total = T_pre-run translation + T_startup/warm-up + repeated execution cost. Say whether deployment or the user pays for the build, and whether the workload runs once or repeatedly.

These are illustrative numbers, not measured benchmarks. AOT costs 600 ms to compile and 5 ms per run. Interpretation costs 0 ms pre-run here and 14 ms per run. JIT costs 180 ms to start, its first 10 runs cost 10 ms each, and its next 90 cost 4 ms each.

For one run:

  • AOT: 600 + 5 = 605 ms

  • Interpretation: 14 ms

  • JIT: 180 + 10 = 190 ms

For 100 runs in one process:

  • AOT: 600 + (100 x 5) = 1,100 ms

  • Interpretation: 100 x 14 = 1,400 ms

  • JIT: 180 + (10 x 10) + (90 x 4) = 640 ms

The fastest choice changes with the workload and boundary. If AOT compilation happened before delivery, its 600 ms may not belong in user startup. If the process ends early, JIT work may not repay its cost. Memory and portability are separate axes: a VM artifact needs its runtime, while a native artifact needs a compatible platform. Neither predicts speed alone.

Bar charts comparing AOT, interpretation, and JIT timing components for one run and 100 runs using illustrative values.

Common traps and the questions that expose them

Trap

Why it fails

Replacement question

Worked-example check

Permanently labelling a language

Implementations choose paths

What artifacts and stages are used?

Every path returns 20

Calling bytecode machine code

It targets a VM

What executes it?

CPython VM dispatches it

Saying an interpreter never compiles

A path can do both

Was an intermediate form compiled?

Python creates an internal form

Assuming JIT always wins

Compilation costs time and memory

Does later work repay warm-up?

One-run JIT is 190 ms here

Mixing startup and throughput

Boundaries differ

One run or repeated work?

Use one 100-run boundary

Saying portability means "runs anywhere"

A runtime or target is still required

What environment is present?

Their required environments differ

Five realistic prompts are: order a mixed pipeline; identify what the CPU directly executes; distinguish bytecode from native code; calculate one-run versus 100-run cost; explain why two implementations choose different paths. Apply the checklist before each answer.

Use programming-language classification questions to test the model instead of memorising labels.

Compiled, interpreted and JIT: the short version and next step

Trace artifacts instead of labelling a language. Locate every translation in time, identify whether the next executor is a CPU or VM, and ask whether runtime profiling can generate native code for later work. For one exact self-check, redraw all three paths for sumEven([3, 8, 5, 12]), preserve the result 20, explain why 100,000 calls total 2,000,000, and recompute the fictional 100-run totals: AOT 1,100 ms, interpretation 1,400 ms, JIT 640 ms. Treat them as arithmetic, not benchmarks. That sequence keeps every comparison properly scoped. Continue with the Java Course and Python Course, then use Coding & Skills for the wider catalog.