---
title: 'Compiler, Interpreter, and Virtual Machine'
source: 'https://academia.sh/en/courses/how-computers-work/compiler-interpreter-and-virtual-machine'
course: 'How Computers Work'
language: en
updated: '2026-08-17T18:08:06+00:00'
license: 'CC BY-SA 4.0'
---

# Compiler, Interpreter, and Virtual Machine

Three models for executing source code, the trade-offs between them, and the distinction between a language and its implementation.

The previous topic established that the processor executes bit patterns in memory as
instructions. A programmer does not write bit patterns; a programmer writes readable
text. A program closes the gap between the two.

This lesson asks: how does the line `result = a + b` turn into instructions the
processor can execute? This lesson defines three models that differ in when the
conversion happens; the following lessons take up the conversion itself, step by step.

## Source Code Is Text

A source file is a sequence of bytes written according to the rules defined in the
character encodings lesson. The `if`, `+`, or variable names inside it carry no
meaning for the computer yet; they are only characters.

There are three ways to turn this text into executable behavior. All three do the same
job; they differ in **when the translation** happens.

## Compilation

A **compiler** translates the entire source text into the target machine's
instructions before execution. The output is a directly executable file.

Consequences:

- There is no translation cost at run time; the program runs directly at processor
  speed.
- Because the compiler sees the whole program, it can optimize extensively.
- A portion of errors are caught before execution, during compilation.
- The output is tied to a specific instruction set and operating system; another
  platform requires recompilation.
- Every change to the source requires a compilation step before it can run.

## Interpretation

An **interpreter** does not translate the source in advance; it reads constructs in
sequence and carries out the corresponding operations itself. Translation continues
while the program runs.

Consequences:

- The same source runs on any platform that has an interpreter for it; no separate
  compilation is required.
- The cycle between writing and running is short.
- Every construct is resolved anew each time it executes; this adds a fixed cost per
  instruction.
- Most errors remain invisible until the corresponding line executes.

This explains why the cache lesson's calculation was not demonstrated with a timing
measurement: when interpretation adds a fixed cost per element, the difference coming
from memory behavior is buried under that cost.

## Virtual Machine and Bytecode

The third model combines the two: the source is translated not into a real processor's
instruction set, but into that of a designed machine called a **virtual machine**. This
intermediate representation is called **bytecode**.

Bytecode is compact and cheap to parse compared to source text, and platform-independent
compared to machine code. The virtual machine is the program that executes bytecode;
writing one virtual machine per platform is less work than writing a separate compiler
for each.

The cost of this layer is speed: bytecode executes slower than native instructions.
**Just-in-time compilation** reduces this cost. The
virtual machine detects frequently executed sections while running and translates them
into native machine code. The translation cost is paid once, and the gain is collected
on every repetition.

Just-in-time compilation has two observable consequences. The first is **warm-up**: a
program's early iterations run slower than later ones, so performance measurements skip
them. The second is access to run-time information: where a compiler must guess which
branch actually runs, a just-in-time compiler can decide by observing.

## Separating a Language from Its Implementation

A common confusion is assuming these models belong to the language itself. The phrases
"compiled language" and "interpreted language" are misleading.

A language is defined by its syntax and semantics; how it executes is a decision made
by the **implementation**. The same language can have both a compiler and an
interpreter, and in practice both are often written. Languages known for being
interpreted have compilers, and languages known for being compiled have interpreters.

The accurate statement is: *this implementation of this language translates the source
into bytecode and executes it on a virtual machine.* This distinction shows which level
a claim about a language's performance actually belongs to.

## Who Translates the Translator

A compiler is itself a program, written in some language. This raises a question that
looks circular at first: what language is a language's first compiler written in?

The answer is simple: another language. The new language's first compiler is written in
an existing language and translates a small subset of it. Once that compiler works, the
language's own compiler can be rewritten in the language itself and built with the
first one. From then on, the compiler compiles itself; this is called a
**self-hosting** compiler.

The same reasoning applies to interpreters and virtual machines. At the bottom of the
chain, there is always a program that already runs on that platform.

The practical consequence: a language's execution model depends not on its definition
but on the history of its ecosystem. A later implementation of the same language can
choose an entirely different model.

## Comparing the Three Models

| Criterion | Compilation | Interpretation | Virtual machine |
|---|---|---|---|
| Translation time | Before execution | Continuously while running | First to bytecode, then while running |
| Portability | Tied to the target platform | Anywhere an interpreter exists | Anywhere a virtual machine exists |
| Execution speed | Highest | Lowest | In between; increases with just-in-time compilation |
| Feedback loop | Requires a compilation step | Shortest | Short |
| Error detection | A portion during compilation | At run time | Between the two |

The boundaries are not sharp. A compiled language can have a runtime library; an
interpreted language can translate the source into an intermediate representation
first. The models are axes that real implementations sit on.

## Seeing the Two Stages

Python stands between the second and third of these three models: the source is first
translated into bytecode, then executed on a virtual machine. The two stages can be
invoked separately:

```python
source = "result = a + b"

code = compile(source, "<example>", "exec")   # first stage: text -> code object
print(type(code))                              # <class 'code'>
print(code.co_names)                           # ('a', 'b', 'result')

environment = {"a": 10, "b": 4}
exec(code, environment)                        # second stage: execution
print(environment["result"])                   # 14

import dis
dis.dis(code)                                  # listing of the generated bytecode
```

The `compile` call executes nothing; it only parses the text and produces a code
object. Execution is started separately with the `exec` call. That the two steps can be
separated is direct proof that translation is a different task from execution.

The instruction names and count in the `dis` output vary by the implementation's
version; bytecode is not a standard but an internal detail of the virtual machine.
What is stable is the existence of the listing: the source text has become a sequence
of instructions before execution.

## Summary

- Source code is text; it must be translated into machine instructions before it can
  execute.
- A compiler translates before execution: it provides speed and compile-time error
  detection, and the output is tied to a platform.
- An interpreter translates while running: it provides portability and a short feedback
  loop, and adds a cost per instruction.
- The virtual machine model translates the source into platform-independent bytecode;
  just-in-time compilation closes the gap by translating frequently executed sections
  into native code.
- Being compiled or interpreted is a property of the implementation, not the language.
- A language's first translator is written in another language; it can later transition
  to a translator that compiles itself.

## Next Step

This lesson addressed when translation happens, not how. The next lesson will open the
source text's path from characters to instructions stage by stage: tokenizing,
extracting structure, semantic checking, and code generation.
