Skip to the content
Software Made Clear Diagrams that show the mechanism About

Compiler vs interpreter

See how compilation and interpretation work together in real toolchains, when code is translated, and which checks can catch errors before execution begins.

5 min read Updated September 23, 2026 Last verified September 23, 2026
ANSWER

Compilation turns code into another representation, such as machine code or bytecode; it can happen before or during execution. Interpretation executes a representation directly. One toolchain may do both. Early errors depend on parsing, static analysis and type checking, not on either label alone.

IN PLAIN TERMS

It is like preparing a recipe in another language, then having a cook follow it. The same kitchen can do both: Python first prepares bytecode, then its virtual machine follows it. Checking the recipe for mistakes is a separate job.

A Python program can be compiled and interpreted in the same run. That sounds contradictory only if the two words are treated as rival labels for languages. A compiler produces another representation of code; an interpreter executes a representation. Translation timing, caching and checking are choices a toolchain can combine in different ways.

Ahead-of-time buildalready translated — reuse the artefactsourcecompileartefactrun 1runs 2 & 3Direct interpretersourcerun 1run 2run 3interpretinterpretinterpretAhead-of-time buildsourcecompileartefactalready translated —reuse the artefactrun 1runs 2 & 3Direct interpretersourcerun 1compilerun 2compilerun 3compile
Two possible workflows, not definitions of two language types. Compilation may also occur at runtime, and an interpreter may execute bytecode that was compiled first.

When the work happens#

The diagram compares two possible workflows, not definitions of compiler and interpreter. The upper row builds a reusable artefact before execution. The lower row shows a direct interpreter processing a representation on each run. A real system may parse or compile source once and then interpret the result; the diagram isolates one timing choice so you can see where work is paid.

Compilation translates a representation into another one. Its output might be machine code, bytecode or an intermediate form. The input can be a module, function or trace rather than a whole program, and translation can happen ahead of time or while the program runs. A build can cache its output, as the upper row shows, but caching is a separate choice. A SQL query planner offers an analogous example: it can prepare and reuse a plan, although the chosen plan can still surprise you.

Interpretation executes a representation directly, perhaps source, an abstract syntax tree or bytecode. It need not read source one line at a time. A parser can inspect an entire code block before any operation runs, and a bytecode interpreter may never see the original source. The lower row illustrates a simple direct interpreter that repeats work on each invocation; it is not a promise that every interpreter repeats parsing or discards every artefact.

Error timing is another independent choice. Parsing can reject malformed syntax before execution in an interpreted toolchain. Static analysis or a type checker can reject code that tests never reach. Dynamic checks happen only when the relevant operation runs. TypeScript checks types before it emits JavaScript; CPython compiles syntax into code objects, yet many name and type errors appear only when the bytecode instruction executes. Neither outcome follows from the word interpreter alone.

Why the line between them is blurry now#

CPython — the reference implementation most people mean when they say “Python” — compiles a source module or code block into a code object containing bytecode before executing it with its virtual machine. Imported modules may reuse cached .pyc bytecode, as the Python FAQ explains. The compile() builtin exposes the same separation: it returns a code or AST object, and exec() or eval() can execute a code object. The compiler and interpreter are successive stages of one implementation. Python’s deque is similarly an implementation detail that may differ between Python runtimes.

Just-in-time compilation translates code during program execution. A runtime may interpret first and compile frequently used paths, or compile a function when it is first requested. LLVM’s ORC JIT supports eager and lazy compilation. Runtime information can help specialization, but JIT output is still compiled code, and its speed advantage is workload-dependent.

So “compiled language” and “interpreted language” are shorthand for an implementation’s execution path, not durable properties of a language. CPython uses a bytecode compiler and interpreter; PyPy can JIT-compile hot paths. For a concrete decision, name the toolchain and ask what it compiles, when it compiles it, what executes the result and which checks run before deployment.

Picking one on purpose#

Compare build latency, startup cost, steady-state performance, deployment artefacts and diagnostics separately. An ahead-of-time build may shift translation cost before deployment and make a reusable artefact. A JIT may spend startup time compiling during execution. An interpreter may still parse or compile before running a code block. Early diagnostics come from the checks the toolchain actually performs, not from a label on the execution engine.

For example, CPython parses a function body before running it, so a syntax error in an untaken branch can stop the program early. A misspelled name in that branch can remain until the branch executes. A separate static analyzer might catch the name sooner. The difference is which checks ran, and with which guarantees. Shipping a bytecode file does not itself prove names, types or business rules are correct.

Use static checks for the properties they can prove and tests for behavior they cannot. A type checker may cover an untaken branch but cannot prove every result is useful. Execution tests exercise concrete paths but can miss branches. Those tools complement each other regardless of whether the final instructions are interpreted or compiled to machine code.

A scanner earlier in the pipeline may recognize token shapes with a deterministic finite automaton; that recognition is separate from the compiler’s later type and logic checks.

Situation Take Because
Catch type errors before shipping Static type checker Checks code before execution.
Fast edit-run loop matters most Short build or incremental compilation Reduces feedback time.
Shipping to machines you do not control Self-contained artefact Explicit deployment dependency choice.
Long-running hot loops JIT It compiles what turns out to be hot.
Situation
Catch type errors before shipping
Take
Static type checker
Because
Checks code before execution.
Situation
Fast edit-run loop matters most
Take
Short build or incremental compilation
Because
Reduces feedback time.
Situation
Shipping to machines you do not control
Take
Self-contained artefact
Because
Explicit deployment dependency choice.
Situation
Long-running hot loops
Take
JIT
Because
It compiles what turns out to be hot.

IF YOU REMEMBER ONE THING

Compilation produces another representation; interpretation executes a representation. A toolchain can use both. Ask separately when translation runs, whether its output is cached, which checks run before deployment and what executes the result.

Questions people also ask

5 QUESTIONS
Is Python interpreted or compiled?

Neither claim is exactly right on its own — it depends which implementation you mean. CPython, the one almost everyone means by "Python," compiles your source to bytecode first and then interprets that bytecode; PyPy JIT-compiles the hot parts of the same program to machine code at runtime. "Compiled" and "interpreted" describe what an implementation does with the language, not a property of the language itself.

Is compiled always faster?

No. Compiling ahead of time avoids paying a translation cost on every run, but a JIT can still win on a long-running hot loop, because it compiles with information — which branch actually runs, what type a value actually turns out to hold — that an ahead-of-time compiler never has. Faster is a claim about one program's shape and runtime, not a guarantee either approach makes on its own.

What does a JIT actually change?

A just-in-time compiler produces executable code during program execution, often for functions or paths that prove worth compiling. Some runtimes begin by interpreting bytecode; others compile on first use. JIT is still compilation, with different timing and available runtime information.

Why do type errors appear at different times?

A static type checker can reject code before execution, whether or not the resulting program will be interpreted. Dynamic checks happen when the relevant operation executes. Parsing and compilation may also reject malformed code early, while an unchecked name or type mismatch can remain until runtime.

What is bytecode?

An instruction set for a virtual machine rather than for a physical CPU — smaller and simpler to work through than source text, but still one translation step short of what silicon runs directly. CPython's .pyc files hold bytecode; the CPython virtual machine is the interpreter that reads it one instruction at a time.

The library Browse all articles