Equivalence of Regular Expressions and Finite Automata

Suppose you want to search a million log lines for anything matching `(https?|ftp)://\S+`. You type this pattern into a text editor and it just works. It runs instantly, even on files far too large to scan by hand-written logic. What actually happens underneath is a translation. Your regular expression is compiled into a finite automaton. The automaton — not the pattern text — does the scanning. This translation is not a programming trick. It is a theorem with two directions, each constructive. Each direction has a clean algorithm you can run by hand on paper. The claim is striking in its symmetry: every regular expression describes exactly the languages that some finite automaton accepts, and vice versa. Neither model is "more powerful" than the other. They are two notations for the same class of languages, the regular languages. One notation (regex) is compact and good for humans to write. The other (automaton) is operational and good for machines to execute. The equivalence theorem is the bridge that lets a compiler, a search tool, or a lexical analyzer accept the human-friendly notation and run the machine-friendly one. This article proves both directions constructively. From regex to automaton, Thompson's construction builds an NFA by structural recursion on the regex. This mirrors exactly how the regex was built from atoms via union, concatenation, and Kleene star. From automaton to regex, the state elimination method (formalized via Generalized NFAs, GNFAs) removes states one at a time. Each removal folds the surrounding transitions into a single larger regular expression. This continues until only a start and accept state with one edge remain. Beyond the mechanics, this equivalence is why regex engines exist at all as a coherent technology. It is also the base case for the whole theory of Closure Properties of Regular Languages. Closure under union, concatenation, and star all trace back to exactly the three regex operators handled here.

Key takeaways
  • Regular expressions and finite automata (DFA/NFA) define exactly the same class of languages: the regular languages.
  • Thompson's construction converts any regex to an NFA recursively, one small automaton per operator, glued with ε-transitions.
  • Thompson's NFA has O(n)O(n) states and transitions for a regex of length nn, built in O(n)O(n) time.
  • State elimination (GNFA) converts any DFA/NFA to a regex by iteratively deleting states and absorbing their transitions into edge labels.
  • State elimination can blow up the regex to exponential length in the worst case — this is a real, unavoidable cost, not a bug.
  • Together with NFA-to-DFA Subset Construction, these constructions form a closed loop: regex ⇄ NFA ⇄ DFA, proving all three models equivalent.
The core insight

A language is regular if and only if it is described by a regular expression if and only if some finite automaton accepts it — and both translations are explicit, structural algorithms, not existence proofs.

The idea

A regular expression over alphabet Σ\Sigma is built from the base cases ∅\emptyset, ε\varepsilon, and each symbol a∈Σa \in \Sigma, combined by union (R1∪R2R_1 \cup R_2), concatenation (R1R2R_1 R_2), and Kleene star (R1∗R_1^*). Each regex RR denotes a language L(R)⊆Σ∗L(R) \subseteq \Sigma^* defined by the obvious recursive semantics. A finite automaton (DFA or NFA) is a 5-tuple whose transition structure accepts or rejects strings by consuming symbols and tracking states, ending in accept or reject. The equivalence theorem states: a language LL is L(R)L(R) for some regular expression RR if and only if LL is accepted by some finite automaton. Since NFAs and DFAs already accept the same languages (by NFA-to-DFA Subset Construction), it suffices to show regex and NFA are interconvertible. This is proved by two independent, explicit constructions, not by a single argument, because the two directions require fundamentally different techniques: one builds up an automaton from syntax, the other tears down an automaton into syntax. The forward direction — regex to NFA — uses Thompson's construction (1968), a structural induction on the regex parse tree. Each syntax rule (union, concatenation, star, and the base cases) has a matching automaton "gadget" with exactly one start state and one accept state, so gadgets compose cleanly via ε-transitions without ever needing to inspect or modify a sub-gadget's internals. The reverse direction — NFA to regex — uses state elimination, formalized through the notion of a Generalized NFA (GNFA): an NFA whose transition arrows are labeled not by symbols but by entire regular expressions, with a unique start state (no incoming edges) and a unique accept state (no outgoing edges, not equal to start). Any DFA/NFA converts trivially into a GNFA, and then GNFA states are deleted one by one, each deletion re-deriving the eliminated state's "detour" as a regex, until two states and one labeled edge remain — that edge's label is the answer.

How it works

Direction 1: Regex → NFA (Thompson's construction)

The construction assigns to every regular expression RR an NFA N(R)N(R) with exactly one start state and one accept state, no transitions leaving the accept state, and no transitions entering the start state. This uniform interface is what makes composition mechanical: to build N(R1∪R2)N(R_1 \cup R_2) you just wire together N(R1)N(R_1) and N(R2)N(R_2) without opening them up.

Base cases:

  • N(∅)N(\emptyset): a single start state and single accept state, disconnected — no string is accepted.
  • N(ε)N(\varepsilon): a single start state that is also the accept state (or two states joined by an ε-edge).
  • N(a)N(a) for a∈Σa \in \Sigma: two states, start →\to accept on symbol aa.

Inductive cases, given N(R1)N(R_1) with (start s1s_1, accept f1f_1) and N(R2)N(R_2) with (start s2s_2, accept f2f_2):

  • Union R1∪R2R_1 \cup R_2: new start state with ε-edges to s1s_1 and s2s_2; both f1f_1 and f2f_2 get ε-edges to a new shared accept state.
  • Concatenation R1R2R_1 R_2: merge (or ε-link) f1f_1 to s2s_2; the new start is s1s_1, new accept is f2f_2.
  • Star R1∗R_1^*: new start/accept state qq with ε-edges q→s1q \to s_1, f1→qf_1 \to q (loop back), and qq directly marked accepting for the empty string.

``` PSEUDOCODE — Thompson(R): regex R -> NFA fragment (start, accept, states, transitions) function Thompson(R): match R: case ∅: create states s, f return Fragment(start=s, accept=f, transitions={}) // f unreachable from s case ε: create states s, f add transition s --ε--> f return Fragment(start=s, accept=f) case symbol a: create states s, f add transition s --a--> f return Fragment(start=s, accept=f) case R1 ∪ R2: F1 = Thompson(R1) F2 = Thompson(R2) create states s, f add transitions s --ε--> F1.start add transitions s --ε--> F2.start add transitions F1.accept --ε--> f add transitions F2.accept --ε--> f return Fragment(start=s, accept=f) case R1 · R2: // concatenation F1 = Thompson(R1) F2 = Thompson(R2) add transition F1.accept --ε--> F2.start return Fragment(start=F1.start, accept=F2.accept) case R1*: F1 = Thompson(R1) create states s, f add transitions s --ε--> F1.start add transitions F1.accept --ε--> s // loop back add transitions s --ε--> f // skip (zero repetitions) add transitions F1.accept --ε--> f return Fragment(start=s, accept=f) ``` Each call creates a constant number of new states and edges relative to the sizes of its recursive sub-fragments (never modifying them internally), so the total automaton size is linear in ∣R∣|R|.

Direction 2: NFA → Regex (state elimination via GNFA)

A GNFA is like an NFA but each transition arrow carries a regular expression, and reading it consumes an entire matching substring in one step. Formally require: a single start state qstartq_{start} with edges out only (no edges in); a single accept state qacceptq_{accept} with edges in only (no edges out); and between every other pair of states (including self-loops) exactly one edge, labeled by a regex (use ∅\emptyset to represent "no such transition" so every pair is covered). Convert an NFA into this shape first: add a fresh start state with an ε-edge to the old start, add a fresh accept state with ε-edges from all old accepting states, and fill in any missing state-pair edges with ∅\emptyset; if multiple parallel edges exist (e.g., multiple symbols between the same two states) union their labels into one regex. The elimination step removes one internal state qripq_{rip} at a time (never qstartq_{start} or qacceptq_{accept}) and reroutes every "detour through qripq_{rip}" as a new direct edge: ``` PSEUDOCODE — ConvertGNFAtoRegex(G): GNFA -> regular expression function ConvertGNFAtoRegex(G): k = number of states in G if k == 2: // only q_start and q_accept remain, one edge between them return label(q_start, q_accept) // pick any state to eliminate, other than start/accept q_rip = choose any state in G.states - {q_start, q_accept} G' = new GNFA with states = G.states - {q_rip} for each qi in G.states - {q_rip}: // qi may equal q_start, never q_accept for each qj in G.states - {q_rip}: // qj may equal q_accept, never q_start if qi == q_accept or qj == q_start: continue // no such edges needed/allowed R1 = label(qi, q_rip) R2 = label(q_rip, q_rip) // self-loop on the ripped state R3 = label(q_rip, qj) R4 = label(qi, qj) new_label = R1 (R2)* R3 ∪ R4 set label(qi, qj) in G' to new_label return ConvertGNFAtoRegex(G') ``` The regex R1(R2)∗R3∪R4R_1 (R_2)^* R_3 \cup R_4 says exactly: "go from qiq_i to qripq_{rip} directly (via R1R_1), loop at qripq_{rip} zero or more times (via R2R_2), leave to qjq_j (via R3R_3) — or skip qripq_{rip} entirely and go directly via the old edge R4R_4." This is precisely the set of all paths from qiq_i to qjq_j in the old GNFA that pass through qripq_{rip} any number of times (possibly zero), re-expressed without needing qripq_{rip} to exist.

Figure
State elimination: removing state q_rip folds its detours into one new labeled edge
Before elimination qi q_rip qj R1 R2 (self loop) R3 R4 (direct edge) ⟹ After elimination qi new label: R1(R2)*R3 ∪ R4

Repeating elimination shrinks the GNFA by one state each round; after k−2k-2 rounds (starting from kk states) exactly the two required states remain, connected by a single edge whose label is a regular expression equivalent to the original automaton.

Complexity & correctness

Thompson's construction — complexity. Let n=∣R∣n = |R| be the number of symbols/operators in the regex. Each recursive case introduces O(1)O(1) new states and edges beyond its children's fragments, and there are exactly nn nodes in the regex's parse tree (one per operator/symbol). So the resulting NFA has O(n)O(n) states and O(n)O(n) transitions, built in O(n)O(n) time by a single bottom-up traversal of the parse tree. This is the key efficiency fact that makes regex engines practical: the automaton size is linear, not exponential, in pattern length — the potential blow-up appears only later, if you then determinize via subset construction.

Thompson's construction — correctness. Prove by structural induction on RR that N(R)N(R) accepts exactly L(R)L(R).

  • Base cases (∅,ε,a\emptyset, \varepsilon, a) are immediate: each gadget accepts precisely the intended one-string (or zero-string) language by direct inspection of its two or three states.
  • Union: a string is accepted by N(R1∪R2)N(R_1 \cup R_2) iff it reaches the shared accept state, which happens iff (via the ε-edges) it is accepted by N(R1)N(R_1) or by N(R2)N(R_2) — by the inductive hypothesis this is iff the string is in L(R1)∪L(R2)=L(R1∪R2)L(R_1) \cup L(R_2) = L(R_1 \cup R_2).
  • Concatenation: a computation accepting in N(R1R2)N(R_1 R_2) must pass through the merge point from f1f_1 to s2s_2 exactly once net (since f1f_1 has no other outgoing edges in the fragment and s2s_2 has no incoming edges from outside), splitting the input into a prefix accepted by N(R1)N(R_1) and suffix accepted by N(R2)N(R_2) — by induction this is exactly membership in L(R1)L(R2)L(R_1)L(R_2).
  • Star: any accepted string decomposes into zero or more trips around the loop through N(R1)N(R_1) (each trip consuming a string in L(R1)L(R_1), by induction) followed by exit to ff, matching L(R1)∗L(R_1)^* exactly; the direct s→fs \to f ε-edge handles the empty-string case.

Because each gadget has a single, uncontaminated entry/exit interface, no case can "leak" acceptance through an unintended path — this is exactly why the one-start/one-accept invariant is maintained explicitly at every step rather than left implicit.

State elimination — complexity. Converting an NFA with kk states to a GNFA is O(k2)O(k^2) (filling in every pair with a label, possibly unioning parallel edges). Each elimination round updates O(k2)O(k^2) edge labels (all pairs excluding start/accept), and there are k−2k - 2 rounds, giving O(k3)O(k^3) regex-construction steps — but the size of the resulting expression can grow multiplicatively at each step (since R1(R2)∗R3∪R4R_1(R_2)^*R_3 \cup R_4 can double label lengths), so the final regex length is bounded only by O(4k)O(4^k) in the worst case — genuinely exponential, and this is provably unavoidable for some automata families, not an artifact of a sloppy algorithm.

State elimination — correctness. The invariant maintained across every elimination round is: for every pair of remaining states (qi,qj)(q_i, q_j), the label on the edge qi→qjq_i \to q_j is a regex denoting exactly the set of strings that drive the original (before any elimination) automaton from qiq_i to qjq_j without passing through any already-eliminated state in between. This holds initially (trivially, no state eliminated yet) and is preserved by each round because R1(R2)∗R3∪R4R_1(R_2)^*R_3 \cup R_4 literally enumerates: either take the old direct route (R4R_4, avoiding qripq_{rip} already by the invariant), or detour through qripq_{rip} zero-or-more times via its self-loop before finally leaving. When only qstart,qacceptq_{start}, q_{accept} remain, the invariant says the single edge label is exactly the language of strings from start to accept avoiding all other (already eliminated) states — i.e., the full language of the automaton, since there are no other states left to avoid.

Honest tradeoff. Thompson's construction is asymptotically optimal (linear) but yields nondeterministic automata that must be determinized (subset construction, up to exponential blowup) before they can be simulated in guaranteed linear time per string. State elimination is the reverse translation and is exponential in the worst case for the regex size it produces — this exponential gap is intrinsic to the succinctness difference between the two representations, not a flaw in either algorithm.

Worked example

Convert the NFA below (accepting strings over {a,b}\{a,b\} ending in `ab`) into a regular expression via state elimination. States: q0q_0 (start), q1q_1, q2q_2 (accept). Transitions: q0→aq0q_0 \xrightarrow{a} q_0, q0→bq0q_0 \xrightarrow{b} q_0, q0→aq1q_0 \xrightarrow{a} q_1, q1→bq2q_1 \xrightarrow{b} q_2.

Step 1 — build the GNFA. Add qstartq_{start} with ε to q0q_0; add qacceptq_{accept} with ε from q2q_2. Merge parallel edges into unioned regex labels.

from \ toq0q_0q1q_1q2q_2qacceptq_{accept}
qstartq_{start}ε∅∅∅
q0q_0a∪ba \cup baa∅∅
q1q_1∅∅bb∅
q2q_2∅∅∅ε

Step 2 — eliminate q1q_1. The only paths through q1q_1 go q0→q1→q2q_0 \to q_1 \to q_2, so we compute the new direct label q0→q2q_0 \to q_2:

R1=a,R2=∅ (no self-loop at q1),R3=b,R4=∅ (no prior direct edge)R_1 = a,\quad R_2 = \emptyset \text{ (no self-loop at } q_1\text{)},\quad R_3 = b,\quad R_4 = \emptyset \text{ (no prior direct edge)}
new label(q0,q2)=a(∅)∗b∪∅=ab\text{new label}(q_0, q_2) = a(\emptyset)^*b \cup \emptyset = ab

(using ∅∗=ε\emptyset^* = \varepsilon, so a⋅ε⋅b=aba \cdot \varepsilon \cdot b = ab, and R∪∅=RR \cup \emptyset = R). Remaining states: qstart,q0,q2,qacceptq_{start}, q_0, q_2, q_{accept}.

from \ toq0q_0q2q_2qacceptq_{accept}
qstartq_{start}ε∅∅
q0q_0a∪ba \cup babab∅
q2q_2∅∅ε

Step 3 — eliminate q2q_2. Only q0→q2→qacceptq_0 \to q_2 \to q_{accept} passes through q2q_2:

new label(q0,qaccept)=ab⋅ε∗⋅ε∪∅=ab\text{new label}(q_0, q_{accept}) = ab \cdot \varepsilon^* \cdot \varepsilon \cup \emptyset = ab

Remaining: qstart,q0,qacceptq_{start}, q_0, q_{accept}.

from \ toq0q_0qacceptq_{accept}
qstartq_{start}ε∅
q0q_0a∪ba\cup babab

Step 4 — eliminate q0q_0. Only path through q0q_0: qstart→q0→qacceptq_{start} \to q_0 \to q_{accept}, with self-loop a∪ba \cup b at q0q_0:

R1=ε,R2=a∪b,R3=ab,R4=∅R_1 = \varepsilon,\quad R_2 = a\cup b,\quad R_3 = ab,\quad R_4 = \emptyset
new label(qstart,qaccept)=ε(a∪b)∗ab∪∅=(a∪b)∗ab\text{new label}(q_{start}, q_{accept}) = \varepsilon (a\cup b)^* ab \cup \emptyset = (a\cup b)^*ab

Result: R=(a∪b)∗abR = (a \cup b)^* ab — exactly "any string ending in `ab`," matching the automaton's intended language. Tracing a sample string, `babab`, confirms: it matches the regex (prefix `bab` matched by (a∪b)∗(a\cup b)^*, suffix `ab`), and stepping it through the NFA lands in q2q_2, the accept state — both models agree.

Common pitfalls & traps

PitfallWhy it happensFix
Forgetting the "skip" branch in starWriting N(R1∗)N(R_1^*) without an s→fs \to f ε-edge rejects the empty stringAlways include the direct start-to-accept edge for zero repetitions
Reusing accept states across sub-fragmentsSharing a state between N(R1)N(R_1) and N(R2)N(R_2) breaks the single-entry/exit invariant, causing spurious acceptanceAlways allocate fresh states per fragment; never mutate a child fragment's interior
GNFA missing a "no edge" caseLeaving a state-pair with no explicit label makes elimination undefinedExplicitly fill every pair with ∅\emptyset before eliminating anything
Eliminating qstartq_{start} or qacceptq_{accept}Breaks the required GNFA shape (unique in/out-free ends), invalidating later stepsElimination order is arbitrary among internal states only, never the two special ones
Assuming the final regex is smallState elimination composes labels multiplicatively; some automata provably force exponential-length regex outputAccept the size tradeoff; use the automaton itself for matching, not the derived regex, when size matters

Pioneers & history

Origins · 1 minKenneth "Ken" Thompson and how this was discovered
K"
Kenneth "Ken" Thompson
born 1943 · Bell Labs, Unix and Plan 9 co-creator

Ken Thompson developed the construction bearing his name while building an editor (`ed`, and later `qed`) at Bell Labs that needed fast regular-expression search. He recognized that compiling a pattern into an automaton — rather than interpreting the pattern text directly — turned matching into simple state-transition lookups, a design choice that shaped every regex engine since, including those inside `grep` and modern text editors.

History
1956
— Stephen Kleene formalizes regular events and proves their equivalence to finite automata, building on McCulloch–Pitts neural nets.
1959
— Rabin and Scott formalize nondeterministic finite automata and prove NFA/DFA equivalence via subset construction.
1968
— Ken Thompson publishes the construction converting regular expressions to NFAs for efficient text search in `qed`/`ed`.
1975
— Aho and Ullman popularize the GNFA/state-elimination technique in compiler-construction pedagogy, formalizing the regex-extraction direction.
2007
— Ken Thompson receives the Turing Award citation for Unix, foundational to the regex-and-shell-tools ecosystem built atop this theory.

Where this matters

Every mainstream regex engine — POSIX `grep`, `RE2`, the matching cores inside Rust's `regex` crate — compiles patterns via a Thompson-style construction specifically to guarantee linear-time matching, side-stepping the catastrophic backtracking that afflicts naive backtracking engines (PCRE-style) on adversarial input. Lexical analyzers generated by tools like `lex`/`flex` take a set of regexes describing tokens (identifiers, numbers, keywords) and compile them into a single combined DFA via Thompson's construction followed by subset construction, giving compilers their tokenizing front end. Network intrusion detection systems (e.g., Snort-style signature matching) compile attack-pattern regexes into automata for line-rate packet inspection, where linear-time guarantees are a security requirement, not a convenience. The state-elimination direction, while less used in production matching, underlies regex minimization and pattern-equivalence checking in formal verification tools, and is the standard proof technique taught alongside Closure Properties of Regular Languages to show regular languages are closed under union, concatenation, and star — since those closures are exactly what Thompson's construction implements operationally.

Ask the tutor: Can you walk me through why the state-elimination order doesn't affect correctness, using a 4-state example?

Are regular expressions and finite automata truly equally powerful, with no exceptions?

Yes, for the standard, "theoretical" definition of regular expressions (union, concatenation, Kleene star, and the base cases ∅\emptyset, ε\varepsilon, symbols). Every such regex has an equivalent finite automaton and vice versa — this is exactly the theorem proved by Thompson's construction and state elimination. Note that many programming-language "regex" libraries add extra features like backreferences or lookahead that go beyond this theoretical model and are not equivalent to finite automata (backreferences alone make matching NP-hard in general). So "regex" in a textbook sense and "regex" in Perl/PCRE syntax are different objects; only the textbook version is proven equivalent to automata here.

Why does Thompson's construction use nondeterministic automata instead of building a DFA directly?

Because the gadgets compose cleanly only if you allow ε-transitions and multiple choices at each step — union, in particular, needs to "guess" which branch to follow, which is exactly what nondeterminism buys you for free. Building a DFA directly from a regex, without going through an NFA, generally requires the more complex Berry–Sethi/position-automaton or derivative-based constructions, which still secretly do similar bookkeeping. Using NFA-to-DFA Subset Construction afterward is the standard second phase; it is deliberately kept as a separate stage since determinizing is exactly where the potential exponential blowup lives.

Does the order in which you eliminate GNFA states affect the correctness or size of the resulting regex?

Correctness is unaffected — any valid elimination order produces a regex denoting the same language, since the elimination invariant holds regardless of which internal state is chosen at each round. Resulting regex size, however, can vary substantially with elimination order; some orders produce much shorter expressions than others for the same automaton. Finding the size-optimal elimination order in general is itself computationally hard, so practical implementations typically use simple heuristics (e.g., eliminate states with fewest edges first) rather than searching exhaustively.

Can state elimination be applied directly to an NFA, or must it first become a DFA?

State elimination works directly on NFAs (and even on GNFAs, which are already more general than NFAs) — determinism is never required. The only structural requirement is the GNFA shape: single start state with no incoming edges, single accept state with no outgoing edges, and exactly one regex-labeled edge between every ordered pair of states. Converting a plain NFA (or DFA) into this shape is a cheap preprocessing step (add fresh start/accept, union parallel labels, insert ∅\emptyset for missing edges) that works identically whether the original automaton was deterministic or not.

Is Thompson's construction still used in real software, or only for teaching?

Both. It remains the standard first stage in production regex engines that guarantee linear-time matching, such as RE2 and Rust's `regex` crate, precisely because it avoids the catastrophic backtracking that plagues naive recursive-descent regex matchers. Compiler lexer-generators like `flex` use it internally before minimizing the resulting DFA. Its pedagogical value and its production value coincide unusually well: the algorithm taught in a first automata course is essentially the algorithm running inside real search tools, modulo engineering optimizations like Unicode handling and DFA caching.

Why can the regex produced by state elimination be exponentially larger than the automaton?

Each elimination round can, in the worst case, roughly double the length of surviving edge labels, because the new label R1(R2)∗R3∪R4R_1(R_2)^*R_3 \cup R_4 concatenates and unions four previous labels. Across k−2k-2 elimination rounds this compounding growth yields an upper bound around O(4k)O(4^k), and specific automaton families (constructed adversarially, e.g., certain counting automata) are known to force genuinely exponential blow-up regardless of elimination order. This is a real limitation of the regex representation's succinctness, not a flaw in the elimination algorithm — automata can be exponentially more compact than any equivalent regex for some languages.

How does this equivalence connect to closure properties of regular languages?

Thompson's construction is, in effect, a constructive proof that regular languages are closed under union, concatenation, and Kleene star: each operator's automaton gadget literally builds the automaton for the combined language from the automata for its parts. This is exactly the toolkit Closure Properties of Regular Languages relies on to show regular languages are also closed under intersection, complement, and other operations, usually by first expressing those operations in terms of union/concatenation/star or by direct product-automaton constructions building on the same NFA machinery introduced here.

Before this

Where this leads