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:
What is the starting artifact: source, bytecode or a native executable?
When does translation happen: before launch or while running?
What executes next: the physical CPU or a virtual machine or interpreter?
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.](https://cdn.knowledgegate.ai/blog-assets/blog_asset_1784564420294_q7m4nd.jpg)
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 msInterpretation:
14 msJIT:
180 + 10 = 190 ms
For 100 runs in one process:
AOT:
600 + (100 x 5) = 1,100 msInterpretation:
100 x 14 = 1,400 msJIT:
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.

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 |
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 |
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.




