Here is a puzzle that sounds like it cannot possibly have an answer. The Go compiler is written in Go. The Rust compiler is written in Rust. But to compile the Rust compiler you need a working Rust compiler — which you were trying to build. To make the first hammer, what did you hit the metal with? A compiler that can compile its own source code is self-hosting, and the process of getting the first one into existence — pulling yourself up by your own bootstraps — is bootstrapping.
The chicken-and-egg only looks unbreakable. The trick is that the first version need not be written in the language itself, and need not be good. Once any working compiler for the language exists — however primitive, in whatever other language — it can compile the real, self-hosted compiler, and from then on the language sustains itself. To see the moves clearly we need a notation that tracks the three languages in play at every step. That notation is the T-diagram.
A compiler involves three languages, and confusing them is the source of every bootstrapping
headache. The source language
Read it aloud as: "a compiler from
Suppose we are creating a new language
The pattern is worth committing to memory. Move 1: write a compiler for a
subset of
Move 3 is not ceremony. When the compiler compiles its own source, the binary it produces should be bit-for-bit identical to the compiler that produced it — a fixed point. This is the famous "three-stage bootstrap" used by GCC and LLVM as a correctness check.
The logic: stage 1 was built by the old compiler, so it might carry subtle differences (it was translated by different code). But stage 2 and stage 3 were both built by an already-new compiler from the same source, so a correct, deterministic compiler must produce identical binaries. A mismatch means a bug — non-determinism, or a compiler that doesn't faithfully reproduce itself. It is one of the most elegant self-tests in all of software.
The same T-diagram machinery answers a second question: how do you get a compiler running on a brand-new
chip that has no compiler yet? You cross-compile. On an existing host machine
By stepping outside the language. The first Fortran compiler (1957) was hand-written in assembly. The first C compiler grew out of B, which grew out of BCPL, which was compiled by earlier tools written in still-older languages — a chain that bottoms out, eventually, at machine code someone entered by hand, toggle switches and all. Every self-hosting language has this ancestry: somewhere back down the line, a human wrote the seed compiler in a different, already-runnable language. Bootstrapping is not circular; it is a spiral that touches down once, on solid ground, at the very start. After that first contact, the language lifts off and never needs the ladder again — which is exactly why languages proudly announce "the compiler is now written in itself" as a rite of passage.
The classic confusion is to think a self-hosting compiler somehow compiled its own first version — an
impossible loop. It did not. Keep the three languages of the T-diagram straight and the paradox
evaporates: the first compiler for language
So "the compiler is written in itself" describes a steady state, reached after the bootstrap — not the origin. At every single moment there was already a working tool doing the compiling; what changed is which language that tool was written in. Mistake the steady state for the beginning and you invent a paradox that history simply never had.
In his 1984 Turing Award lecture, Ken Thompson posed a chilling question: could you trust a compiler you did not write? He described planting a backdoor not in a program's source, but in the compiler — teach it to recognise when it is compiling, say, the login program, and silently insert a hidden password. Then go further: teach the compiler to recognise when it is compiling itself, and re-insert both hacks into the new compiler binary. Now delete the malicious source entirely. The backdoor lives on, invisibly, propagated through every future self-compilation. Inspect all the source you like — the source is clean — yet every compiler descended from that poisoned binary carries the attack.
Thompson's moral: "You can't trust code that you did not totally create yourself" — and since no one writes their own compiler, CPU microcode and silicon, trust ultimately rests on the whole toolchain, not the source alone. It is bootstrapping's shadow: the very self-reproduction that lets a language stand on its own also lets a lie reproduce itself forever. Modern work on reproducible builds and "diverse double-compiling" is the compiler community's answer to the ghost Thompson raised.