Foundations of mathematics
Recursive functions and Turing machines
Formal models that define exactly which functions can be computed by an algorithm.
IntuitionWhat can a machine compute?
A calculator can add and multiply; a compiler can check types; an AI can (sometimes) answer questions. But is there a function that no algorithm, however clever, can ever compute? Alan Turing's answer — yes — came from a precise mathematical model of "algorithm": the Turing machine, a tape, a read/write head, and a finite table of rules. Two seemingly different formalizations, recursive functions (built from simple pieces by composition and recursion) and Turing machines (a mechanical step-by-step process), turn out to compute exactly the same class of functions — strong evidence for the Church–Turing thesis that this really is "everything computable".
UndergraduatePrimitive recursive and -recursive functions
Definition: Primitive recursive functions
The primitive recursive functions are the smallest class of functions containing the zero function, the successor , and all projections, and closed under composition and primitive recursion: given and , the recursion , defines a new primitive recursive . Addition, multiplication, exponentiation, and every "for-loop" program with a fixed bound are all primitive recursive — and every primitive recursive function is total (defined on all inputs) and halts on a bounded number of steps for each input.
To capture all computable functions — including ones that might not halt — we add one more operator. The **-recursive (general recursive) functions add unbounded minimization**: returns the least such that , searching — and simply never returns if no such exists. This is exactly what makes -recursive functions capable of not halting, and Kleene's theorem shows they compute exactly the same class of (partial) functions as Turing machines.
| Class | Built from | Always halts? | Example |
|---|---|---|---|
| Primitive recursive | Composition + bounded recursion | Yes, always total | exponentiation |
| General (-)recursive | Primitive recursion + unbounded | No, may loop forever | Ackermann function |
| Turing-computable (partial) | State + tape + transition rules | No, equals -recursive exactly | Any algorithm whatsoever |
There is no algorithm that, for every program index and input , always halts and correctly outputs whether (running program on input ) halts.
Why is it true?
This is the mathematical reason why no antivirus, compiler, or IDE can ever perfectly detect infinite loops, dead code, or "this function always crashes" in full generality — not a limitation of today's engineering, but a hard mathematical wall.
Proof
Suppose, for contradiction, that such a decider exists: if (halts) and if (runs forever), and itself always halts with the correct answer.
Using , build a new program that, on input : computes ; if , then enters an infinite loop; if , then halts immediately. is built effectively from (just plus an if-statement and a loop), so it has some program index , i.e. .
Now ask the self-referential question: does halt?
Case 1: if halts, then by correctness of , . But by the definition of , makes loop forever on input — i.e. does not halt. Contradiction.
Case 2: if does not halt, then by correctness of , . But by the definition of , makes halt on input — i.e. does halt. Contradiction.
Both cases are contradictory, so the assumption that exists is false. The Halting Problem is undecidable.
AdvancedRice's theorem
The Halting Problem is just one example of a much broader phenomenon. Call a property of partial computable functions semantic if it depends only on the function computed by program , not on the source code itself, and nontrivial if some computable function has it and some does not.
For every nontrivial semantic property of partial computable functions, the set is undecidable.
Why is it true?
This single theorem instantly rules out algorithms for "does this program compute the zero function", "does this program compute a total function", "are these two programs equivalent", and countless other natural questions about program behavior — all in one stroke, without a separate diagonal argument for each.
Proof
WLOG assume the everywhere-undefined function (computed by a program that never halts on any input) does not have property — otherwise argue with the complement property , which is decidable exactly when is. Since is nontrivial, fix some program whose function does have property .
Suppose, for contradiction, that is decidable by some algorithm (given a program index, halts and correctly reports whether that program's function has property ). We reduce the Halting Problem to , contradicting Theorem 1.
Given any pair , effectively construct (by simple textual manipulation — Kleene's s-m-n theorem) a new program that, on any input : first simulates program running on input ; if that simulation ever halts, then goes on to simulate program on input and outputs whatever it outputs.
Now examine the two possibilities. If halts on : the simulation of on finishes, so then behaves exactly like on every input, i.e. — which has property (since is semantic, it only depends on the function computed, and has ). If does not halt on : the simulation of on never finishes, so never reaches the -simulation step on any input ; hence is the everywhere-undefined function , which by assumption does not have property .
So: halts on has property answers "yes". Since is computable, the algorithm "compute from , then run " would decide the Halting Problem — contradicting Theorem 1. So no such exists: is undecidable.
UndergraduateReal-World Applications and Worked Examples
Compiler optimizers must decide things like "is this code reachable?" or "does this variable's value matter?" — by Rice's theorem, these are undecidable in full generality, which is exactly why real compilers use conservative approximations (they may keep some genuinely dead code rather than risk deleting live code). The Busy Beaver function — the largest number of steps an -state halting Turing machine can take before stopping — is a concrete, uncomputable function: known values are , , , , and in 2024 the collaborative Busy Beaver Challenge (led by Tristan Stérin and collaborators, with a Coq-verified proof from a contributor using the pseudonym "mxdys") established — showing that even this "simple" combinatorial question is only computable case-by-case, never by a general algorithm.
Example: Unfolding the Ackermann function
Using the rules , for , and for , compute step by step, and explain why the Ackermann function is total but not primitive recursive.
Solution
by the third rule. We first need , and by the second rule.
(unfolding twice with the second and first rules). So , hence .
, and . So . Hence .
Back to the top: ; unfolding ; so . So .
The Ackermann function is proven total (it always eventually reduces to case ), so it belongs to the general recursive functions — but it grows faster than every primitive recursive function (e.g. , is already a tower of exponents). Since one can show every primitive recursive function is eventually dominated by some fixed , no primitive recursive function can equal itself — a diagonal-style domination argument, not the -operator, is what puts Ackermann outside primitive recursion despite being total.
Example: Reducing the Halting Problem to "does this program print hello?"
Show that the problem "given a program , does running (on no input) ever print the string `hello`?" is undecidable, by a direct reduction from the Halting Problem — without invoking Rice's theorem.
Solution
Suppose, for contradiction, an algorithm decides whether running ever prints `hello`. We use to decide the Halting Problem, contradicting Theorem 1.
Given any program and input , effectively build a new program (no input needed) that: simulates running on ; if/when that simulation halts, then prints `hello` and stops.
If halts on : the simulation finishes, so reaches the print step and does print `hello`. If does not halt on : the simulation never finishes, so never reaches the print step and never prints `hello`.
So halts on answers "yes". Since is computable, "build then run " decides the Halting Problem — contradicting Theorem 1. So cannot exist: the "prints hello" problem is undecidable. (This is exactly the compiler-analysis pattern: "is this line of code ever reached" has the same shape.)
Using , what is ?
Which of these is a consequence of the undecidability of the Halting Problem for compiler design?
Which property is Rice's theorem NOT applicable to?
In the diagonal proof of Halting undecidability, what leads to the contradiction?
References
- Wikipedia contributors (2024). Halting problem
- Wikipedia contributors (2024). Rice's theorem
- Wikipedia contributors (2024). Ackermann function