The Halting Problem
Build a program whose only purpose is to disagree with the oracle about itself, and the oracle cannot exist.
Skip to the animationNo program can decide, for every program and input, whether that program halts — proved by constructing a program whose only purpose is to disagree with the supposed decider about itself.
The claim, stated precisely
We want a program H(P, x) that always halts and correctly answers "does P halt on x?". Note H is not allowed to simply run P — if P loops, so would H, and H must always return an answer.
The proof turns on one observation: a program is a finite string, so it can be passed to another program as data. That is not a trick — it is why compilers, interpreters and debuggers exist. In particular, H(P, P) is a perfectly sensible question.
The contradiction
- 1Assume H exists.
- 2Build D: given a program P, ask H whether P halts on P, then do the opposite — loop if H says halts, halt if H says loops.
- 3D is clearly constructible: it is a call to H and an if-statement.
- 4Now ask what D(D) does.
- 5If H says D(D) halts, then D loops. H was wrong.
- 6If H says D(D) loops, then D halts. H was wrong again.
- 7There is no third case, so H cannot exist.
This does not say halting is hard to decide, or that we lack a clever enough algorithm. It says no algorithm exists, and no future machine changes that.
Recognisable but not decidable
The halting language is still recognisable: simulate P on x, and if it halts you will eventually observe it. What is impossible is recognising the non-halting case — no amount of waiting distinguishes "has not halted yet" from "never will".
| Class | Guarantee |
|---|---|
| Decidable | some machine always halts with the correct yes or no |
| Recognisable | halts and accepts on members; may loop forever on non-members |
| Neither | not even that — the complement of halting is an example |
Rice's theorem, and why your tools hedge
Halting is not a lone curiosity. Rice's theorem says every non-trivial property of what a program computes is undecidable — "does this throw?", "do these two functions agree?", "is this line ever reached?", "does this leak memory?".
That is why every real tool approximates. A type checker rejects programs that would in fact have run fine. A linter reports unreachable code conservatively. No compiler will tell you your loop terminates. These are not engineering failures to be fixed in the next release — they are this theorem, and the only question is which direction a tool chooses to be wrong in.
The standard way to prove some new problem undecidable is reduction: show that if you could solve it, you could solve halting. Since you cannot, you cannot solve the new one either.
Watch it work
Check yourself
question 1 / 4
One question at a time. Pick an answer to see why it is right or wrong, then move on — there is no score to keep and nothing is saved.