Encodings and Enumerators
Suppose you want to feed a Turing machine into another Turing machine, as data. Machines are abstract mathematical objects with states and transition tables. But the tape of a Turing machine only holds symbols from a finite alphabet. To reason about machines that process machines — compilers, verifiers, interpreters, self-referential arguments — we must first fix a way to write any such object down. It must become a finite string over a fixed alphabet. This is the problem of encoding. It is the quiet piece of bookkeeping that makes every deep result in computability theory expressible at all. Once objects are strings, a second question arises. What does it mean to generate all strings in a language? This differs from merely testing whether a given string belongs to it. This is the role of the enumerator, a device that prints a (possibly infinite) list of strings. It never stops if the language is infinite. Enumerators formalize the intuitive notion of "listable" or "recursively enumerable" sets. They connect the algorithmic notion of recognition to the combinatorial notion of listing. These two ideas — encoding and enumeration — are not mere technicalities. Real systems depend on them constantly. Source code is an encoding of a program that a compiler (itself a machine) reads as a string. A search engine's crawler enumerates the web without ever "deciding" it is complete. A proof assistant enumerates theorems derivable from axioms. The encoding convention for a machine is what lets us build a <span class="wiki-link-missing">Universal Turing Machine</span>. This is a single machine that simulates any other machine given as input. It is the technical foundation beneath the Church-Turing Thesis's claim that "algorithm" has one true mathematical meaning. This article makes precise how to encode arbitrary discrete objects (graphs, automata, machines) as strings. It defines enumerators rigorously, and proves the central equivalence theorem: a language is Turing-recognizable if and only if some enumerator lists it. We build the encoding-decoding machinery step by step. We give a full pseudocode construction converting a recognizer into an enumerator and back. We also trace a worked example over a small language.
- Encoding fixes a way to represent any finite discrete object (graph, DFA, TM) as a string over a fixed alphabet, written .
- Encodings must be effective (computably constructible) and decodable, so machines can manipulate objects as data.
- An enumerator is a Turing machine variant with a printer that outputs strings of a language, one after another, possibly forever.
- Theorem: a language is Turing-recognizable iff some enumerator enumerates it — recognizers and enumerators have equal power.
- The "recognize ⇒ enumerate" direction needs dovetailing (interleaved simulation) so no single input can starve the process.
- Encodings and enumerators together underlie the Universal Turing Machine and every diagonalization argument in Decidable and Turing-Recognizable Languages.
Once every object is a string via a fixed encoding, "recognizable" and "enumerable" become the same computational power viewed from two directions: testing membership versus generating a list.
The idea
An encoding of a discrete mathematical object (a graph, a Turing machine, a polynomial, a proof) is a string over some fixed finite alphabet , produced by an effective, injective procedure: distinct objects get distinct strings, and there is an algorithm that reconstructs from . The specific bit-level details of an encoding scheme are never important — what matters is that some reasonable encoding exists, because any two reasonable encodings are interconvertible by a Turing machine in a way that changes no decidability or recognizability question. This is why textbooks freely write things like for a graph paired with an integer, trusting the reader to accept that graphs, integers, and tuples can all be serialized into one string. When we say a Turing machine takes "another machine " as input, we mean formally that receives the string and must decode the transition table, state set, and start/accept/reject states from it before simulating . An enumerator is a variant of the Turing machine model equipped with an extra output device — a printer — attached to a second, write-only tape. An enumerator has no explicit input; it runs forever (or halts) starting from a blank work tape, and periodically it copies the current contents of a work tape onto the printer tape followed by a special separator symbol, effectively "printing" a string. The language enumerated by , written , is the set of all strings that appear on the printer output at some point during the (possibly infinite) run. If never halts but keeps printing forever, it may still enumerate a language completely, as long as every string of eventually appears — it is allowed (indeed expected, for infinite languages) that no finite prefix of the run has printed everything. Two enumerators can enumerate the same language while printing strings in wildly different orders, with or without repetition; none of this affects which language is enumerated, only the algorithm's internal behavior. This freedom is what lets an enumerator built from a recognizer use a slow, repetitive strategy (dovetailing) and still correctly enumerate the target language, which is the crux of the main theorem below.
How it works
The equivalence between recognizers and enumerators is proved by two explicit constructions, each turning one kind of machine into the other.
Direction 1 — Enumerator ⇒ Recognizer. Given an enumerator for language , build a recognizer : on input , simulate , and each time prints a string, compare it to ; if it matches, accepts. If runs forever without printing (because ), simply never halts — which is fine, because recognizers are only required to halt and accept on strings in ; they may loop on strings not in .
Direction 2 — Recognizer ⇒ Enumerator (the subtle direction). Given a recognizer for , we want to build an enumerator that prints exactly the strings accepts. The naive idea — go through in the standard order of all strings over , run on to completion, then on , and so on, printing whenever accepts it — fails, because might not halt on (recall is only guaranteed to halt on strings in ). The construction would hang forever on the first non-halting input and never reach , even if they are in . The fix is dovetailing: instead of running each simulation to completion before starting the next, run all of them simultaneously, each for a bounded number of steps, in a carefully staggered order that guarantees every pair is eventually tried. ``` ENUMERATOR-FROM-RECOGNIZER(M): // M is a Turing machine (recognizer) for language L. // Produces an enumerator E that prints exactly the strings in L, // each at least once, in some order (possibly with repeats). let w_1, w_2, w_3, ... be all strings in Sigma* in standard order (length order, then lexicographic within each length) for i = 1, 2, 3, ... : // outer counter: "round" i for each pair (p, q) with p + q = i, p >= 1, q >= 1: // p indexes which string, q is the step budget simulate M on input w_p for exactly q steps if M accepts w_p within those q steps: print w_p // if M rejects or has not halted within q steps: do nothing, move on // E never halts (unless L is finite and we choose to stop early, // which is optional and not required) ``` Every string is accepted by in some finite number of steps . The pair appears at round in the double loop, so is guaranteed to print at that round (and possibly again at later rounds, which is harmless). Strings not in are never printed, because never accepts them, regardless of how many steps we simulate. Hence exactly. The diagram below shows the dovetailing schedule as a grid: rows are candidate strings , columns are step budgets , and the diagonal sweep order is how actually visits cells.
The core mechanism generalizes far beyond this pair: dovetailing is the standard technique whenever infinitely many potentially-nonhalting processes must all be given a "fair share" of computation. It reappears in constructing the Universal Turing Machine simulation strategy for time-bounded diagonal arguments and in enumerating all provable statements of a formal system.
Complexity & correctness
Correctness of Direction 1 (enumerator ⇒ recognizer). accepts exactly when eventually appears in 's output, i.e., exactly when . If , never prints , so never finds a match and runs forever — consistent with being a recognizer (not necessarily a decider) for . No false accepts are possible since only accepts on an exact string match against 's genuine output.
Correctness of Direction 2 (recognizer ⇒ enumerator). Let .
- Soundness (): only prints when its simulation of on reaches an
accepting configuration within the allotted steps — a genuine, faithfully simulated accept of . Hence every printed string is truly in .
- Completeness (): Fix any . Since recognizes , halts and accepts
after some finite number of steps . The double loop enumerates every pair with exactly once, organized by increasing sum ; since there are only finitely many pairs with a given sum, every specific pair — including — is reached after finitely many rounds. At that point simulates on for at least steps and detects the accept, so is printed. Together these give , establishing the Enumerator-Recognizer Equivalence Theorem: a language is Turing-recognizable if and only if it is enumerable by some enumerator. This is exactly the theorem that justifies the alternate name "recursively enumerable" for Turing-recognizable languages, tying directly into the classification of languages developed alongside Decidable and Turing-Recognizable Languages.
Complexity, honestly. These constructions are about computability, not efficiency — there is no polynomial-time bound to state here, and none is claimed. The dovetailed enumerator can be catastrophically slow: to print a single short string discovered only after needs steps, must first complete rounds through , wastefully re-simulating many other candidates from scratch (a naive implementation restarts each simulation at step 1 every round, giving a step count that grows roughly quadratically in the round index for a fixed candidate, i.e., overall super-linear blowup relative to 's own running time). This is an accepted cost: the theorem is purely about which languages are enumerable, not about doing so quickly. A refined version keeps partial configurations across rounds instead of restarting, saving redundant work, but the asymptotic classification (recognizable ⟺ enumerable) is unaffected either way, since we only need some enumerator to exist.
Encoding correctness. For an encoding scheme to be sound for these arguments, it must be: 1. Total and effective — an algorithm computes from any valid . 2. Injective (up to a fixed canonical form) — distinct objects map to distinct strings, so decoding is unambiguous. 3. Decodable — an algorithm recovers (or detects "invalid string") from . Any two encoding schemes satisfying these properties are interchangeable via a computable translation, so no computability result (recognizability, decidability, or the enumerator equivalence above) depends on which reasonable encoding is chosen — only on the fact that some reasonable encoding exists.
Worked example
Let , over , a classic non-regular but decidable (hence certainly recognizable) language. Take to be a straightforward Turing machine deciding by repeatedly crossing off one leading and one trailing , running in time on inputs of length (each pass is , and passes are needed) — but for this example the exact runtime doesn't matter, only that halts on every input (accepting strings of the form , rejecting all else). Standard order over begins:
so Only (), (), (), etc. belong to . Because is a decider here (it always halts), the trace is simpler than the worst case, but it still shows the dovetailing schedule concretely. The table shows which round first causes to discover and print each in-language string, assuming (for illustration) that needs steps as listed.
| in ? | (steps needs) | round printed | ||
|---|---|---|---|---|
| 1 | yes | 1 | 2 | |
| 2 | no | — | never | |
| 3 | no | — | never | |
| 4 | no | — | never | |
| 5 | yes | 4 | 9 | |
| 6 | no | — | never | |
| 7 | no | — | never | |
| ... | ... | ... | ... | ... |
| 11 | yes | 9 | 20 |
Even though (round 2) and (round 9) are far apart in output order, still reaches each of them eventually because every finite pair sits on some finite diagonal. The output stream of is therefore interleaved with the many wasted rounds spent on strings never destined to be accepted — those rounds simply produce no printed output and are silently skipped. This matches the general argument: soundness (only true members are printed) and completeness (every member is eventually printed) both hold, exactly as claimed above.
Common pitfalls & traps
| Pitfall | Why it's wrong | Fix |
|---|---|---|
| "Run to completion on , then , …" without dovetailing | Hangs forever if doesn't halt on some early ; later strings in are never reached | Interleave simulations with a bounded step budget per round (dovetailing) |
| Treating enumerators as requiring sorted/no-repeat output | The definition only requires every string of to appear at some point; order and duplicates are irrelevant | Define as the set of strings ever printed, not a sequence |
| Assuming an enumerator for must halt | For infinite , must run forever to print infinitely many strings; halting is only expected/optional for finite | Only require completeness of the (possibly infinite) output set |
| Confusing "encodable" with "decidable" | Every finite object over a finite alphabet is trivially encodable; encodability says nothing about whether membership questions about it are decidable | Keep encoding (a syntactic bookkeeping step) separate from decidability (a semantic computability property) |
| Assuming a decider built from an enumerator halts on all inputs | The enumerator-to-recognizer construction only yields a recognizer: it may loop forever on | To get a decider, you need and its complement each enumerable (co-recognizable), by a separate theorem |
Pioneers & history
The encoding of machines as data, and the enumerator/recognizer duality, trace back to the founding of computability theory in the 1930s, when the very notion of an "algorithm" had to be pinned down mathematically before anyone could ask what algorithms cannot do.
Origins · 1 minAlan Turing and how this was discovered
Where this matters
Encoding schemes are the invisible plumbing behind every system where programs treat other programs as data: compilers parse source code (an encoding of a program) into an abstract syntax tree; virtual machines like the JVM load bytecode (an encoding of a program for a specific machine model) and execute it; version-control and build systems serialize dependency graphs and configuration objects as strings for storage and transmission. The very idea of a Universal Turing Machine — one machine that simulates any other machine given as encoded input — is only meaningful because a robust, decodable encoding convention exists; this is the theoretical seed of every general-purpose computer, which is itself a physical universal machine executing encoded programs. Enumerators show up wherever systems must generate all instances of something without ever declaring "done": web crawlers enumerate reachable pages, theorem provers enumerate consequences of an axiom set, and property-based testing tools enumerate candidate inputs. The equivalence between recognizing and enumerating clarifies a subtle limitation these systems share: a search process that can verify a "success" case is a recognizer, and it can always be turned into a generator of successes by dovetailing — but it can never be upgraded into a machine that also reliably reports failure (halts and rejects) unless extra structure (co-recognizability) is available. This distinction is the direct ancestor of the harder undecidability results explored under Decidable Problems About Automata, where recognizing an automaton property is easy but deciding it, or its negation, can be genuinely impossible.
Ask the tutor: How does the dovetailing idea generalize to build a Universal Turing Machine that simulates any encoded machine on any encoded input?
Why can't we just say a Turing machine "reads" a graph or another machine directly?
Turing machines are defined to operate on strings over a finite tape alphabet — that is the entire input/output interface the model provides. A graph, automaton, or another machine is a richer mathematical structure (sets, relations, transition tables) that has no tape representation on its own. Encoding supplies the missing bridge: a fixed, effective, decodable procedure that serializes the structure into a string , so a Turing machine can legitimately "read" it by reading that string and running a decoding subroutine first. Without this step, statements like "a Turing machine takes another Turing machine as input" would be meaningless; with it, they become precise and provable, which is exactly what underlies the construction of a Universal Turing Machine.
Does the specific choice of encoding scheme matter for the theorems?
No, as long as the scheme is effective, injective, and decodable. Any two such schemes for the same class of objects are computably intertranslatable: a machine can read an object encoded under scheme A and output its encoding under scheme B, and vice versa. Since decidability and recognizability are closed under this kind of computable pre/post-processing, no result about whether a language is decidable or recognizable can flip depending on which reasonable encoding you picked. This is why textbooks state theorems using generic angle-bracket notation without ever pinning down the exact bit layout — the layout is a free implementation choice, not a mathematical commitment.
Is every enumerable language automatically decidable?
No. Enumerability is exactly equivalent to Turing-recognizability, which is strictly weaker than decidability in general. A language is decidable exactly when both and its complement are enumerable (recognizable); if only is enumerable but its complement is not, is recognizable but undecidable. The classic example is the acceptance problem : it is enumerable (dovetail-simulate every machine on every input and print accepting pairs) but not decidable, because no algorithm can always correctly detect non-acceptance in finite time.
Why does the dovetailing construction sometimes print the same string more than once?
Because different (p, q) pairs can all detect the same accept: once M accepts w_p within q0 steps, it also "accepts within q steps" for every q ≥ q0, so every later round that revisits index p will re-detect and re-print w_p. The formal definition of the language enumerated by E, L(E), is defined as a set of strings that appear at some point, so duplicate printings are harmless — they don't change which set is enumerated. If a duplicate-free enumerator is wanted for some downstream use, one simply adds a memory table of already-printed strings and skips reprinting, which is easy but not required by the definition.
How does this connect to the phrase "recursively enumerable" used in older textbooks?
"Recursively enumerable" (r.e.) is an older name, from before the standardization of "Turing-recognizable," for exactly the same class of languages: those for which some algorithm can list all and only their members. The name reflects the historical framing via general recursive functions rather than Turing machines, but the Church-Turing Thesis guarantees these formalizations define the same class. The Enumerator-Recognizer Equivalence Theorem is precisely the statement that justifies using "recursively enumerable" and "Turing-recognizable" interchangeably: a language is listable by some enumerating procedure exactly when a Turing machine can recognize (accept members of, possibly loop on non-members) it.
Can an enumerator enumerate the empty language?
Yes — an enumerator for simply never prints anything, for example a machine that immediately enters an infinite loop without ever invoking its printer. This is consistent with the definition: is the set of strings ever printed, and the empty set of printed strings is a perfectly valid (if unexciting) output. Correspondingly, the trivial recognizer that rejects (loops on) every input recognizes , confirming the equivalence holds even at this degenerate boundary case.