Introduction
Some limits on computers come from cost: a task takes too long or needs too much memory. The halting problem is a different kind of limit. It asks whether a given program, run on a given input, will eventually stop or run forever. No program can answer that question correctly for every program and input, and the reason is a short argument you can follow in full. This worked example takes a claimed halt-checker seriously, builds a program from it, and shows step by step why the claim has to be false.
The argument is a proof by contradiction. You assume the thing you want to rule out, follow the assumption carefully, and arrive at something impossible. When that happens, the assumption must be false.
The Problem
Suppose someone announces that they've written a program called HALTS. It takes two inputs: the text of any program, which we'll call P, and an input for it, which we'll call X. HALTS always finishes, and it always answers correctly with one of two words:
- "halts," if P, given X, eventually stops
- "loops," if P, given X, runs forever
The claim is about every program and every input, not just the ones anyone has tested. Your task is to decide whether such a program could exist.
One fact about programs makes the argument possible. A program is itself a piece of text, so it can be given as input to another program, or even to itself. Programmers do this routinely. A spell-checker can check its own source code, and a compiler, the program that turns code into something a machine runs, can process its own code.
Working It Through
Step 1: Build a new program, CONTRARY, that uses HALTS
If HALTS exists, anyone can use it as a part inside a larger program. Build a new program called CONTRARY. It takes one input, the text of some program P. The first thing CONTRARY does is ask HALTS a question: what will P do when P is given its own text as input? In other words, CONTRARY runs HALTS with P as the program and P again as the input.
Nothing about this step is unusual. Programs call other programs all the time, and P's text is a legal input because it's just text. If HALTS works as claimed, this question always gets an answer in a finite time.
Step 2: Make CONTRARY do the opposite of the prediction
Now give CONTRARY its instructions for each possible answer:
- If HALTS answers "halts," CONTRARY goes into an endless loop, for example by repeating "add 1" with no way out.
- If HALTS answers "loops," CONTRARY stops at once.
So CONTRARY is built to do the reverse of whatever HALTS predicts about a program run on itself. Every piece of it is ordinary: a call to HALTS, a check of the answer, and either a loop or a stop. If HALTS exists, CONTRARY can be written.
Step 3: Run CONTRARY on itself
Here is the move that makes the argument work. CONTRARY is a program, so it has a text. Give CONTRARY its own text as input.
Follow what happens. CONTRARY receives its own text as P. It asks HALTS what CONTRARY does when given CONTRARY's text as input. That's the very run now under way. HALTS has to predict the very run that's asking it the question. This is self-reference: making a program act on a description of itself.
Step 4: Trace both cases
HALTS always finishes and gives one of two answers, so there are only two cases to check.
Case 1: HALTS answers "halts." Then, by its instructions in Step 2, CONTRARY goes into an endless loop. So CONTRARY, run on itself, never stops. HALTS said it would halt. HALTS was wrong.
Case 2: HALTS answers "loops." Then, by its instructions, CONTRARY stops at once. So CONTRARY, run on itself, does halt. HALTS said it would loop forever. HALTS was wrong again.
There's no third option. HALTS was assumed to give one of the two answers and always be right, yet each answer is wrong in this one case. The run of CONTRARY on itself is a case HALTS can't get right, whatever it says.
Step 5: Conclude that HALTS can't exist
Go back through the chain. If HALTS exists, CONTRARY can be built (Steps 1 and 2). If CONTRARY exists, it can be run on itself (Step 3). And that run makes HALTS give a wrong answer (Step 4). But HALTS was supposed to be right every time. The assumption that HALTS exists leads to a contradiction, so the assumption is false.
Notice that nothing in the argument depended on how HALTS works inside. It could use any method at all. The argument shows that every possible halt-checker fails on at least one program and input. In the language of computability theory, the halting problem is undecidable: no algorithm answers it correctly for every possible input. The argument is close to a popular proof presented in the Stanford Encyclopedia of Philosophy entry on Turing machines, which also notes that Turing's own 1936 results concerned closely related questions (De Mol 2025, sec. 2.4).
Key Considerations
Particular programs versus all programs. The argument doesn't say you can never tell whether a particular program halts. For many programs, you can. A program that counts to 10 and then stops will halt, and people prove facts like this about specific programs every day. What the argument rules out is a single method that gives the right answer for every program and every input. Any checker that works on many programs will still fail, or fail to answer, on some.
The common mistake: thinking faster computers could solve it. It's natural to assume a hard computing problem is a matter of speed, and that a faster machine or more memory would eventually get there. That reasoning doesn't apply here. In the argument, HALTS was allowed to be as fast as you like and still failed. The contradiction comes from what HALTS is asked to do, not from how long it takes. A computer a billion times faster could run HALTS a billion times faster and still be wrong about CONTRARY.
Why "just run it and see" doesn't work. Another tempting idea is to run P on X and watch. If it stops, the answer is "halts." But if it hasn't stopped after an hour, or a year, you still don't know whether it's about to stop or never will. Watching can confirm halting, but it can't confirm looping forever.
Self-reference isn't a trick. Feeding a program its own text can look like cheating. But programs are text, and programs routinely process text, including other programs. The self-referential run is a legitimate input, and a checker that claims to handle every input has to handle it.
Summary
Assume a perfect halt-checker exists. Build a program that does the opposite of whatever the checker predicts about it, and run that program on itself. Both of the checker's possible answers turn out wrong:
| If HALTS says CONTRARY, run on itself, will... | CONTRARY then... | So HALTS was... |
|---|
| halt | loops forever | wrong |
| loop forever | stops at once | wrong |
Every answer HALTS can give is wrong, so no program HALTS can exist. The halting problem is undecidable.
- The two rows cover every possible answer, because HALTS was assumed to always finish with "halts" or "loops."
- The middle column follows from CONTRARY's instructions in Step 2.
- The last column compares the prediction with what happened, and it reads "wrong" in both rows. That's the contradiction.
References
- De Mol, Liesbeth. 2025. "Turing Machines." Stanford Encyclopedia of Philosophy. First published 2018; substantive revision May 21, 2025.