Can a program analyze arbitrary source code and decide, in finite time, whether it will halt or loop forever? In 1936, Alan Turing proved the answer is no.
The proof is a short exercise in self-reference: you construct a program that uses the hypothetical decider against itself, forcing a contradiction.
Why It Matters
The result draws a hard line on what automated tools can guarantee. Compilers, linters, and static analyzers can catch large classes of bugs, but they can never detect all infinite loops or all unreachable code in arbitrary programs. Detecting whether a given piece of code loops forever is exactly the halting problem, so any tool that claims to catch every such case would need a halting decider, which, as the proof below shows, cannot exist.
Setup
To prove something is impossible, you start by assuming it is possible, and then show the assumption leads to a contradiction.
Let’s assume we managed to write a perfect function, will_halt(program, input). It takes the source code of any program and its input data, analyzes it without executing it, and returns a boolean: true if it halts, false if it loops forever. And will_halt itself never freezes. It always terminates with an answer.
Counter-Engine
To break this, Turing constructed a deliberate contradiction. Let’s call it devious.
devious takes the source code of any program as an argument. It passes that code into our perfect will_halt function, asking a specific question: “What happens if this program is fed its own source code as input?”
Once it gets the answer, devious does the exact opposite:
function devious(program_code) {
if (will_halt(program_code, program_code)) {
while (true) { /* loop forever */ }
} else {
return; // halt immediately
}
}
If the predictor says the code will finish, devious spins off into infinity. If the predictor says it will loop, devious immediately returns and stops. It is explicitly engineered to make a liar out of will_halt.
Contradiction
What happens when you pass devious into itself?
devious(devious_code); // where devious_code is devious's own source
Let’s step through what will_halt has to decide inside that call:
- If
will_haltreturnstrue(“this will halt”), then theifbranch runs.deviousenters the infinite loop and never halts. The predictor is wrong. - If
will_haltreturnsfalse(“this will loop forever”), theelsebranch runs.devioushalts immediately. The predictor is wrong again.
Notice what will_halt is actually being asked here. Inside the call devious(devious_code), the line will_halt(devious_code, devious_code) runs. That’s asking: “does devious halt when given its own source as input?” and we have exactly called devious with devious_code as input.
There is no third option. will_halt cannot return true, and it cannot return false. The function is logically impossible to implement.
Conclusion
This isn’t a limitation of our current tools. It’s a theorem. Turing didn’t just find an edge-case bug; he proved that algorithmic prediction of program behavior has a hard, mathematical ceiling. No procedure, static or dynamic, can decide whether an arbitrary program halts.
In practice, this ceiling rarely matters. We build good-enough heuristics (linters, type checkers, bounded model checkers) and accept that “works for all practical inputs” is not the same as “provably correct for all inputs.”