TM·IX Limits of computation Chapter 56 of 65
A conversation with the Oracle
A chapter in dialogue. Your opponent is the Oracle, who claims he can read the text of any program and tell you whether it will halt. You argue with him until he gives up: you build a program that does the opposite of whatever he answers, and find that neither cunning, nor small programs, nor modest questions can save him. Along the way: Hilbert in Königsberg, the quine from Chapter 0 used as a weapon, the busy beaver tamed in 2024, and a Collatz-like problem hidden in a six-state machine.
Limits of computation
- 54 Automata
- 55 Turing machine
- 56 Undecidable you are here
- 57 P vs NP
- 58 Hard problems
Builds on: 55 · The Turing machine 00 · What a program can do
What you will take away
- prove that the halting problem is undecidable, and explain the proof both through the diagonal and through self-reference
- reduce one problem to another, and use Rice’s theorem to recognize questions about programs that no analyzer can answer
- understand what real code analyzers, antivirus programs and type checkers do instead of the impossible: approximations, limits and restricted languages
9Are there problems no computer can solve?
The last chapter ended with a question: does every precise question have an answer that a machine can find? One such question has already come up three times. In Chapter 0 it was three lines about Collatz, and nobody knows whether they halt for every number. In Chapter 53 it was the wish for a check stronger than type checking: a program that reads any program and says whether it will hang. In the last chapter it was a simulator that can only wait. The answer to the last chapter’s question is no, but you needn’t take that on trust: we will reach it by arguing with someone who is sure of the opposite.
The chapter is written as a dialogue, after the model of Douglas Hofstadter’s Gödel, Escher, Bach (1979). Between the chapters of that book, Achilles and the Tortoise, borrowed from Lewis Carroll, hold conversations, and the book’s hardest ideas run through their arguments. The one you will be talking to is the Oracle. That word is borrowed too: in 1938, in the dissertation he wrote at Princeton under Church, Turing considered machines that can turn to an “oracle” with a hard question. Turing doesn’t explain how the oracle works, only that its answers are always right. Our Oracle claims to be such a device.
Königsberg, September 1930
The day before, Gödel had knocked out one half of this confidence: not every true statement can be proved. That story is told in “Mathematics, the Queen of the Sciences,” in the chapter on Gödel. The other half, that every precise question can be settled mechanically, was refuted six years later by Church and Turing. Their answer is where our conversation is heading.
Prelude. The Oracle’s shop
Oracle. Come in. Give me the text of any program, and I’ll tell you whether it halts or runs forever. Always. Without a single mistake.
You. All right. while True: pass.
Oracle. Forever. A child could tell you that.
You. And the path of 27 under the Collatz rule?
Oracle. It halts after 111 steps. I traced it step by step.
You. So you run the program. And if it hasn’t stopped, how long do you wait before you say “forever”?
Oracle. A hundred thousand lines. Whatever hasn’t finished in a hundred thousand never will.
This is the first and most natural attempt: run the program and wait. The course module cs.oracle contains an Oracle of this kind. The function halts(source, steps) runs the program, counting lines, and if the program hasn’t finished within steps lines, it answers “won’t halt.” He isn’t hard to catch.
The patient program executes two lines per pass through the loop, the test and the increment, 120,002 lines in all, and that doesn’t fit into a budget of a hundred thousand. The Oracle declared it endless, though it finishes in a fraction of a second. Any budget breaks the same way: for a limit of a million there is a program that needs a million and one lines.
Here is the question we are arguing about, in strict terms. The halting problem is to determine, from the text of a program and its input, whether the program will finish. A problem with a yes-or-no answer is called decidable if some program halts on every input and gives the right answer. The whole weight of the definition rests on two words. “Every”: the Oracle has to cope with every program, including the ones he dislikes. “Halts”: “wait a little longer” doesn’t count as an answer.
Oracle. I was joking about the hundred thousand lines. I don’t run programs at all. I understand them. How is a trade secret.
You. Have it your way. Then we’ll make you a function: halts(program) returns True or False, always, and always correctly. How the function works inside doesn’t interest me. I want to know whether it can exist at all.
Oracle. By all means. Ask away.
Act one. The contrarian
You. Here is a program. I’ll call it the contrarian. The first thing it does is ask you about itself: will the contrarian halt? If you say “it halts,” it goes into an endless loop. If you say “it doesn’t,” it finishes at once.
Oracle. Show me.
You. Well, will the contrarian halt?
Oracle. Let me see… If I say “yes,” it loops. So the right answer is “no.” But if I say “no,” it halts. So the right answer is… Hold on.
You. I’ll wait. But remember our deal: you have to answer.
Try playing the Oracle yourself. Answer whatever you like, as many times as you like, and change your strategy from one round to the next.
The Oracle always loses, and not because he chose badly. The contrarian is built to do the opposite of any answer. So the Oracle cannot give a correct answer to “will the contrarian halt?”, and yet a correct answer exists: the contrarian either halts or it doesn’t. Only one way out is left: no such Oracle exists. Here is the same thing stated rigorously.
There is no program halts(P) that, for every program P, halts and correctly answers whether P halts.
Suppose there is such a program. Write the contrarian, a program D that calls halts(D), gets an answer in finite time (as promised), and does the opposite: on True it loops forever, on False it halts. How a program can pass itself to halts is the subject of the next act; it can be done.
What does halts(D) answer? If True, then D, by its construction, loops forever, and the answer is wrong. If False, then D halts, and the answer is wrong again. halts has no third option: it must halt and answer. That is a contradiction, so the assumption is false.
The proof is shorter than this paragraph, and that is where its strength lies: it doesn’t depend on how halts works inside. Whether it is simple or elaborate, whether it tries cases or was trained on a million programs, its contrarian is built in the same way. Keep in mind what the theorem does not say. It doesn’t claim that nothing can be learned about whether a particular program halts; for while True: pass you can tell at a glance. What doesn’t exist is a single method that works for all programs.
Act two. The mirror
Oracle. I’ve found a hole in your argument. Your contrarian asks me about itself, so it hands me its own text. But if a program contains its own text, then that text contains a program, which contains the text… It’s infinite. No such program exists, and there’s nothing to argue about.
You. You’re reasoning the way I did in Chapter 0. There was a two-line program there that prints itself.
In Chapter 7 we took apart how a quine works: a template of the program with one hole, and into the hole goes the written form of the template itself. The same device lets a program get hold of its text for any purpose; printing is only one of them. Here is a contrarian you can run. It asks the Oracle what it is going to print, “yes” or “no”, and prints the opposite. It’s the same trap with “what will I say?” in place of “will I halt?”, which makes it easier to check. First we hand it to the honest Oracle from cs.oracle, who answers by running the program, and then to two Oracles who answer without thinking.
The first four lines of output are the contrarian itself, a finite and perfectly ordinary program. To find out what it prints, the honest Oracle runs it; the program asks the Oracle again, and the Oracle runs it again. He will never answer, and we stop him on the fourth round. The two “dishonest” Oracles answer at once, and both are wrong: the contrarian says the opposite. The last line confirms that the contrarian handed the Oracle its own text, character for character.
What worked here always works. In 1938 Stephen Kleene proved the recursion theorem: whatever transformation of program texts we take, there is a program that gets its own text and applies that transformation to it. Printing itself is a special case, the transformation “change nothing”; that is the quine of Chapter 0. Asking the Oracle about itself and doing the opposite is another.
For every program $T$ that takes the text of a program and an input, there is a program $R$ that on every input $x$ does the same as $T$ on the pair (the text of $R$, $x$). Informally: every program may assume it has been given its own text.
This is a deeper answer to the course’s first big question. A program can print itself, but that is the least of what it can do: any program can get hold of its own text and do anything with it, print it, hash it, send it to the Oracle. In computing, self-reference is an ordinary tool, not a paradox. The paradox arises only for someone who has promised to answer every question.
Act three. The diagonal
Oracle. Fine, I can’t beat the contrarian. But it’s a single freak, made on purpose to spite me. I promise to answer for all normal programs, and on contrarians let me be wrong. There can’t be many of them.
You. A contrarian grows out of any Oracle all by itself, along the diagonal. I’ll draw you a table, and you’ll see how.
All programs can be written out in one infinite list: programs are finite texts, and finite texts can be ordered by length and, among texts of the same length, alphabetically. Let $P_0, P_1, P_2, \ldots$ be this list. Number the inputs too: $0, 1, 2, \ldots$ Now make an infinite table in which cell $(i, j)$ says whether program $P_i$ halts on input $j$. If the Oracle existed, he could fill in any cell.
Walk down the diagonal, through the cells $(0, 0), (1, 1), (2, 2), \ldots$, and build a program $D$ that on input $n$ does the opposite of what cell $(n, n)$ says: if $P_n$ halts on input $n$, $D$ loops forever, and vice versa. $D$ differs from $P_0$ on input 0, from $P_1$ on input 1, from every $P_n$ on input $n$. So $D$ is not in the list. But the list holds every program, and $D$ is a program: if the Oracle exists, such a program is easy to write, since it only has to call him on each diagonal cell. The contradiction is the same as with the contrarian, and no wonder: the contrarian is $D$ asked about itself.
Georg Cantor used the same argument in 1891 to prove that there are more real numbers than natural numbers: see day three of “Infinities” in the math course. And it has a consequence stronger than the theorem itself. Programs form a countable set: they can be numbered. But there are as many yes-or-no problems about natural numbers as there are subsets of the natural numbers, since each problem is the set of numbers for which the answer is yes. Cantor’s diagonal shows that there are uncountably many such sets. There aren’t enough programs to go around, and undecidable problems are the overwhelming majority. What is strange is rather the reverse: how often the problems we meet in practice turn out to be decidable.
Act four. Modest questions
Oracle. I give up on halting. Nobody needs that question anyway. I’ll be more modest. For instance, I’ll tell you whether a program prints the word “hello”. There’s nothing to loop on there: either it printed it or it didn’t.
You. Then I’ll turn you back into the old Oracle. Give me any program P. I’ll write a program Q that runs P silently and, when it finishes, prints “hello”. Q prints “hello” if and only if P halts. I’ll ask you about Q and learn whether P halts.
Oracle. But I agreed a minute ago that nobody can know whether a program halts.
You. That’s the point.
This argument is called a reduction. Problem A reduces to problem B if every input of A can be mechanically turned into an input of B with the same answer. Then a solver for B would solve A as well. And the other way round: if A is undecidable, then so is B, or else the solver for B would solve A. Reduction is the main tool of this part of the course: in the next chapter it will be used to prove that a problem is hard.
The reduction to the “hello” question fits in three lines:
The word “silently” isn’t there for decoration. If P prints “hello” on its own and then hangs, Q must not pass that answer off as its own. And if P crashes with an error, that counts as halting too, and Q still has to reach its last line. Turning the scheme into working code is the task “The reduction” at the end of the chapter. The same scheme works for any other “modest” question.
None of the workshop’s questions mentions halting. Will there be a division by zero? Will the program go online? Will it return 42? Will it delete a file? These are questions about what a program does. And the same scheme worked for each of them: run P silently, then do whatever the question asks about. In 1951 Henry Gordon Rice noticed that this works for every such question and proved it in his dissertation; the theorem was published in 1953.
Suppose a property of programs depends only on what a program does (which inputs it turns into which outputs, and on which inputs it loops forever), not on how it is written. If some programs have the property and others don’t, then no program can tell, from the text of an arbitrary program, whether it has the property.
Suppose the program that never halts lacks the property (otherwise take the negation of the property, which is nontrivial too). Take a program $W$ that has it. From any program $P$ build $Q$: on input $x$ it first silently runs $P$ and then does the same as $W$ on $x$. If $P$ halts, $Q$ behaves the same as $W$ and has the property. If it doesn’t, $Q$ does nothing forever, like the program without the property. A detector for the property, applied to $Q$, would tell whether $P$ halts, and that is impossible.
Rice’s theorem is a short answer to a great many practical questions. There is no perfect antivirus: “the program infects other files” is a property of behavior. Fred Cohen, one of the first people to study computer viruses, was already running experiments in 1983, as a student of Leonard Adleman (the “A” in RSA from Chapter 60). In a 1984 paper (the journal version came out in 1987) Cohen proved that no algorithm can detect every possible virus. There is no perfect type checker: “the program never adds a string to a number” is a property of behavior, and that is why mypy in Chapter 53 rejected innocent programs. For the same reason no linter will find all the dead code, no optimizer will produce the best possible program, and no tests can prove that a program has no bugs.
And yet analyzers exist and earn their keep. Each one breaks one of the Oracle’s three promises (always to answer, to answer correctly, to answer about any program), and it chooses which. Some don’t always answer: “I don’t know” is an answer too, and checkers that ask you for type hints work this way. Others make mistakes, but on the safe side. A type checker rejects whatever looks doubtful (better a false alarm than a missed bug), while a signature-based antivirus, the other way round, lets unfamiliar programs through but almost never raises a false alarm. A common case is a time limit, the Oracle’s strategy from the prelude, only admitted openly: when time runs out, a trustworthy tool says “I don’t know,” and a hasty one risks being wrong. And the third kind changes the language. If programs are written in a language that isn’t Turing complete, like the Starlark configurations from the last chapter, Rice’s theorem doesn’t apply to them, and a great deal becomes decidable.
No analyzer can answer the question “what will this program do?” in general. Every working tool, be it a compiler, a linter, an antivirus or a type checker, either sometimes stays silent, or sometimes errs, or works with a restricted language. When a tool promises everything at once, it is keeping something back.
Act five. The beavers
Oracle. One last offer. I’ll answer only about small programs. There are finitely many Turing machines with two states: I’ll go through them all and learn the answers by heart. Then three states, four…
You. Two, by all means. Five can be done as well, but it took people more than sixty years. And for six nobody knows how. Let me show you the beavers.
We met them on the sixth level of the game in the last chapter. In 1962 Tibor Radó asked: how many steps can a Turing machine with $n$ states and the symbols 0 and 1 make, started on a blank tape, if it does eventually halt? The maximum is called the busy beaver number $BB(n)$, and a machine that reaches it is a champion.
The machines below are written the way the bbchallenge project writes them: one row per state, and in each row one rule per symbol, saying what to write, which way to move and which state to go to; Z means halt. The cs.turing module from the last chapter reads this notation with the function standard.
Six steps, twenty-one, a hundred and seven. The five-state champion hasn’t halted after a million steps and isn’t about to: it needs 47,176,870. In the course sandbox that would take seconds, and on a busy server it might not fit into a cell’s time limit. A browser needs only a fraction of a second, and in the race below you can run the champion all the way to the end.
Five states took that much effort, and six may never yield, because the beavers are the halting problem in different clothes.
The function $BB(n)$ is uncomputable: there is no program that, given any $n$, outputs $BB(n)$.
Suppose there is such a program. Then we could decide whether any machine $M$ halts on a blank tape: if $M$ has $n$ states, compute $BB(n)$ and run $M$ for $BB(n) + 1$ steps. If it hasn’t halted by then, it never will, or else it would beat a record that is the maximum by definition. But halting on a blank tape is undecidable: the general halting problem reduces to it, because the input can be “baked into” the machine as a few extra states that write it onto the tape.
The same argument shows that $BB(n)$ grows faster than any computable function: if some computable $f$ had $BB(n) \le f(n)$ for all $n$, then $f$ could take the place of $BB$ in the proof. The numbers bear this out. For six states it is known that $BB(6)$ is at least $2 \uparrow\uparrow\uparrow 5$, a tower of twos whose height is itself a tower of twos 65,536 high. And there is a machine with 745 states that halts only if ZFC, the system of axioms on which nearly all of mathematics rests, is inconsistent. If ZFC is consistent, it cannot prove that this machine never halts, and so it cannot pin down $BB(745)$ either. Here the halting problem meets Gödel’s theorem from Königsberg.
Act six. Collatz
Oracle. But six states is a tiny machine. What could there be in it that can’t be taken apart?
You. The conjecture you saw in Chapter 0. Or rather, a close relative of it.
At the end of June 2024, when the proof for five states was already finished, the bbchallenge participant mxdys reported a six-state machine whose behavior looked random, and soon a participant who goes by Racheline worked out what it does. The machine was named Antihydra. Stripped of detail, it runs this process:
Over and over, the number is multiplied by one and a half with any half dropped, and the counter gains two points for every even value and loses one for every odd one. The machine halts if the odd values ever outnumber the even ones by more than two to one, because then the counter goes below zero. If the parity of the numbers behaves like a coin, odd values are on average as common as even ones, the counter grows, and getting below zero becomes harder and harder. Most likely Antihydra never halts. But proving that means solving a problem of the same kind as the Collatz conjecture, a question about how a simple arithmetic rule behaves at every step at once. Mathematics doesn’t yet know how to solve such problems, and so $BB(6)$ runs into an unsolved problem before undecidability even enters the picture.
The link between Collatz and this chapter goes deeper than coincidence. In 1972 John Conway, the inventor of Life from the last chapter, looked at generalized Collatz rules of the form “if $n$ leaves remainder $r$ when divided by $m$, replace it with $a_r n + b_r$,” where the coefficients are fractions chosen so that the result stays a whole number. Conway proved that for such rules the question “will the number reach one?” is undecidable: any Turing machine can be programmed in them. Later he built the language FRACTRAN on this idea, in which a program is nothing but a list of fractions. The Collatz conjecture itself may turn out to be true, false or unprovable; but the question it belongs to is undecidable in general.
Finale. The shop closes
Oracle. Let me count my losses. I can’t answer about halting: the contrarian. I can’t pretend that contrarians are rare: the diagonal. I can’t answer modest questions about behavior either: halting reduces to them. Even small machines don’t save me: at six states you already need a conjecture that nobody knows how to prove.
You. And yet you give excellent answers about most of the programs people bring you. Keep your sign, but change one word: “often” instead of “always.” And allow yourself to say “I don’t know.”
Oracle. Is that what everyone who checks programs does?
You. Everyone you can trust.
Yes, and that is a theorem, not a temporary weakness of our technology. There are precise yes-or-no questions that no program will ever answer, however much time and memory you give it. The chief one is the halting problem: telling from the text of a program whether it will finish. The proof fits in a paragraph. If an Oracle program existed, you could write a contrarian that asks it about itself and does the opposite; the contrarian gets hold of its own text the way the quine of Chapter 0 does. The Oracle has no correct answer about the contrarian, so there is no Oracle.
“No program” here means “no computer.” Chapter 54 showed a machine about which everything can be known, the finite automaton, and where its powers end. Chapter 55 showed that a Turing machine computes everything any computer computes and, by the Church–Turing thesis, everything that can be computed mechanically at all. This chapter showed that even the Turing machine cannot answer questions about its own kind. From one undecidable problem, reductions produce others: by Rice’s theorem every nontrivial question about the behavior of programs is undecidable, so there is no perfect antivirus, type checker or optimizer. The busy beaver function is uncomputable, and undecidable problems outnumber decidable ones: there are countably many programs and uncountably many problems.
Tasks
Three tasks. In the first you become the contrarian: your program has to get its own text without reading a file and fool any Oracle. In the second you turn the reduction from Act four into working code, and the tests probe the traps it usually falls into. In the third you write a beaver simulator fast enough to run millions of steps.
Write the contrarian program. It must get its own text, character for character, pass it to the function predict from the module cs.oracle (import it with from cs.oracle import predict), and print a single word: “no” if the Oracle answered “yes”, and “yes” in any other case. Ask the Oracle once and only once. The tests replace predict with different Oracles, simple ones and cunning ones, and check that the program asked about its exact text and did the opposite. You may not read your own file or use inspect, sys, os and similar modules.
This is the quine from Chapter 7, except that the text goes into a variable instead of being printed. Make a template s, the text of the whole program with %r where the written form of the template itself belongs, and get the program as s % s.
In the template every % sign except %r is doubled: the line me = s % s appears in the template as me = s %% s. Line breaks inside the template are written \n.
You can test yourself without the Oracle: temporarily replace the last line with print(me, end=""), and you get a quine whose output must match the program. If it doesn’t, the tests will also show you the first difference. Then put the last line back; in the template it must read the same as in the program.
Four lines, and the whole of Kleene’s theorem is in them: the program got hold of its text without reading anything and did what it pleased with it. The tests brought in Oracles that always answer “yes”, that always answer “no”, that go by whether the text has an odd length, and that look for the word “no” in it; none of them guessed right. Nor will any other: whatever the Oracle says, the contrarian prints the opposite. And the Oracle from cs.oracle, who runs the program to find out, never answers at all: try running the solution without the tests.
Write make_q(p): given the text of a program p, return the text of a program Q that prints “hello” if and only if p halts, and prints nothing else. “Halts” means finishes in any way: by reaching its end, by crashing with an error, or by calling sys.exit(). If p doesn’t halt, Q runs forever too and prints nothing. The tests run Q in a separate Python process and give endless programs one second. The programs p in the tests don’t read anything from the keyboard.
The starter breaks in three places. If p prints something itself, its output ends up in the output of Q. If p crashes or calls sys.exit(), the last line is never reached. And if p contains quotes or indentation, the glued text may not even compile.
Don’t paste the text of p into Q as code. Paste it as a string, through repr, as in the quine, and execute it inside Q with exec. Then neither quotes nor indentation can get in the way.
Someone else’s output can be silenced: contextlib.redirect_stdout(io.StringIO()) sends everything printed into a string that nobody reads. Errors and sys.exit() are caught by except BaseException: SystemExit is not an Exception, so an ordinary except Exception lets it through.
Three traps, three lines of defense: repr turns any text into a safe string literal, redirect_stdout silences the other program’s output, and except BaseException catches both errors and an exit through sys.exit. These are the details that the word “silently” hides in the chapter’s scheme, and they are also where reductions usually break on the first try. If you had an analyzer that always correctly said whether a program prints “hello”, then analyzer(make_q(p)) would answer the question of whether p halts. So no such analyzer exists.
Write bb_run(code, limit), a simulator for Turing machines in bbchallenge notation. The notation is a row for each state, A, B, C…, separated by _; each row has three characters for each tape symbol, 0 and 1: what to write, which way to move (L or R), and which state to go to. State Z (or H) means halt: a rule that leads there is carried out like any other and counts as a step. The triple --- means “no rule”: the machine halts without making a step. The machine starts in state A on a tape of zeros. Return the tuple (steps, ones on the tape) if the machine halted within limit steps, and None otherwise. The tests run champions and decoy machines, and the last test gives you five seconds for two million steps of the five-state champion.
The starter is almost right, but it counts one step too few: halting by a rule with Z is a step too. The two-state champion should give (6, 4).
Two more cases: halting on H instead of Z, and the triple ---, on which the machine halts without a step. For speed, parse the notation once, before the loop: turn it into a list of rules indexed by state and symbol, where the state is a number and the move is +1 or −1.
A dictionary tape works, but for two million steps a list is faster: make a list of zeros with room to spare and put the head in the middle, and when the head reaches an end, extend the list. You can count the ones as you go or recount them at the end in one line, whichever you prefer.
Parsing has moved out of the loop, states have become numbers, the tape a list, and the ones are counted on the fly: ones += write - tape[head] adds one when a 0 turns into a 1 and subtracts one when it goes the other way. This way two million steps of the champion take a fraction of a second in the course sandbox, and all 47 million take a few seconds; the race in the chapter computes the same thing in the browser. One more point: unlike the Oracle, bb_run is allowed to answer “I don’t know” (None), and that alone is why it can exist. A function that said “never halts” instead of None would compute $BB$.
What next
The border is drawn: there are problems no machine will ever solve. But the other side of the border isn’t all sunshine either. Take a formula with a hundred logical variables joined by “and,” “or” and “not,” and ask whether they can be given values that make it true. This problem is decidable in the plainest possible way: try every assignment of values, since there are finitely many. The catch is that there are $2^{100}$ of them, about $1.3 \cdot 10^{30}$, and a computer checking a billion assignments a second would need about three thousand ages of the universe to get through them.
Decidable doesn’t mean doable: some problems would keep you waiting longer than the universe has existed. Can such a problem be solved quickly, without trying everything? Checking a ready answer is easy: substitute the values into the formula. But finding one? In 1956 Kurt Gödel wrote a letter about this to John von Neumann, the man who had once taken him aside in Königsberg after his announcement. Von Neumann was dying by then, and the question in the letter is still open. It is the subject of the next chapter.