The idea: translate first, or carry out as you read

A computer only runs machine code. A program written in a high-level language has to get there in one of two ways. A compiler translates the whole program into machine code before it runs, and saves the result as a file (a.out). An interpreter is itself a program, already compiled, that reads your program and carries it out while it runs, one piece at a time. The page runs the same four-line program both ways, on one clock.

Top row: sum.py goes into the compiler, which writes a.out with x86-64 machine code; a.out then runs directly on the CPU. Bottom row: sum.py goes into the interpreter, a program that runs on the CPU and walks a syntax tree of sum.py, doing what each node says.
The compiler is gone by the time the program runs; the interpreter stays between the program and the CPU for every statement.
total = 0
for i in range(1, N + 1):
    total = total + i
print(total)

Both sides read the same source and build the same kind of syntax tree. After that they part ways. The compiler checks every name, then generates seven x86-64 instructions with total in register eax and i in ecx. The interpreter keeps the tree and walks it: for each node it fetches the node, dispatches on its type (Assign, For, Call…) and evaluates it, reading and writing names in an environment table.

Where the time goes

The page counts work in ticks. The compiler pays up front: lex 2, parse 3, check 3, codegen 12, a 20-tick build before anything runs. The program it made then costs 1 tick to set up, 1 tick per loop iteration (four instructions: add, inc, cmp, jl) and 1 tick to print. The interpreter starts after a 3-tick parse, but every statement costs 3 ticks (fetch, dispatch, evaluate), and every iteration costs 5: the For node tests and advances i (2) and the body runs (3).

Ncompiled: build + runinterpreted: parse + runprints first
3 (Demo 1)20 + 5 = 253 + 21 = 24interpreter
4 (Demo 5)20 + 6 = 263 + 26 = 29compiled
1000 (Demo 2)20 + 1002 = 10223 + 5006 = 5009compiled, about 5× sooner

So the compiled total is 22 + N and the interpreted total is 9 + 5N. A short script finishes first when interpreted, because it never pays the build. Any real amount of work finishes first when compiled, because the build is paid once and the interpreter's overhead is paid on every statement. In Demo 2, when the compiled program prints at t = 1022, the interpreter is only at iteration 203.

Line chart of total ticks against loop size N from 0 to 8. The compiled line starts at 22 and rises 1 per iteration; the interpreted line starts at 9 and rises 5 per iteration. They cross between N = 3 and N = 4.
Compiled: 22 + N ticks; interpreted: 9 + 5N. The interpreter wins only below the crossing, here between N = 3 and N = 4.

The ratio in the page's model is 5 to 1. Real tree-walking interpreters are often 10 to 100 times slower than optimised machine code on tight loops; the build, on the other hand, can take seconds or minutes for a large program. Editing the source changes nothing for the interpreter, but the compiler must build again before the edited program can run (Demo 5).

When errors are found

The typo program adds a branch that prints a name that does not exist:

    if total > 100:
        print(totl)

The compiler checks every line before anything runs, including branches that may never run. At t = 8 it stops with error: 'totl' undeclared: no a.out, nothing runs (Demo 3, Demo 4). The interpreter only looks at a line when it gets there. With N = 3 the total never passes 100, the branch never runs, and the program prints 6 without anyone noticing the bug (Demo 3). With N = 1000 the branch runs in iteration 14, when total = 105, and the program crashes with NameError: name 'totl' is not defined at t = 121 (Demo 4). That is why programs in interpreted languages lean on tests and linters: they find what a compiler's checks would have found.

What the output is tied to

The compiler's output is machine code for one instruction set and one operating system: here an ELF file with x86-64 instructions. Copied to an ARM64 machine, the kernel refuses it: cannot execute binary file: Exec format error (Demo 6). To run there, the program must be compiled again for ARM64, or cross-compiled. The interpreted program is just text: it runs on any machine that has a build of the interpreter, and the interpreter is the part that was compiled for each CPU.

compiled ahead of timeinterpreted
start-upwait for the buildat once
speed per statementa few CPU instructionsfetch, dispatch, evaluate every time
errors like an undefined namebefore the program runswhen the line runs, maybe never
after an editbuild againrun again
what you shipa binary per CPU and OSsource (plus the interpreter)

Most languages are both

"Compiled language" and "interpreted language" describe implementations, not languages. Most real systems mix the two. A bytecode VM compiles the source to a compact instruction set for a virtual machine, then interprets that: CPython writes .pyc files, javac writes .class files for the JVM. A JIT (just-in-time compiler) starts by interpreting and compiles the hot code to machine code while the program runs, so it gets a quick start and fast loops: HotSpot, V8, PyPy.

languageusual implementation
C, C++, Rust, Gocompiled ahead of time to machine code
Java, C#compiled to bytecode, then interpreted and JIT-compiled by the VM
Python (CPython), Ruby (CRuby)compiled to bytecode, then interpreted (recent versions add a JIT)
JavaScriptparsed to bytecode, interpreted, hot functions JIT-compiled (V8, SpiderMonkey)

See also How a Java Program Is Compiled and Run for every javac phase and the JVM interpreter, HotSpot JIT Tiers for how a VM compiles the hot code while the program runs, and How a C Program Is Compiled, Linked, Loaded and Started for what happens between the compiler and a running a.out.

A lexer is a DFA built from the token regexes; see Regex to NFA to DFA.

One back-end pass, mapping variables onto the few CPU registers, is animated in Register Allocation.

See Inside a Tiny Compiler for a lexer, parser, code generator and stack VM built step by step.

What the page leaves out

Linking and loading (the page's a.out just appears), optimisation levels, register allocation, bytecode and its instruction set, JIT tiers and deoptimisation, garbage collection, and static vs dynamic typing, which is a separate question: a compiler for a dynamically typed language still cannot catch every type error, and some interpreters check names before they run. The tick costs are made up to keep the numbers small; real ratios depend on the language and the code.