Regex to Automaton, Explained

After reading this you will be able to read a regular expression as a small machine, trace that machine over an input string character by character, and predict which strings it accepts and which it rejects.

What a regex really is

A regular expression looks like text, but it describes a machine. The machine has a set of states and labeled arrows between them. You start in one state, read the input one character at a time, and follow an arrow that matches each character. If you finish reading in an accepting state, the string is accepted. If not, it is rejected.

Take the regex a(a|b)*b over the alphabet {a, b}. In words: start with an a, then any mix of a and b (including none), then a final b. The string ab matches. So does aaabb and abb. The string ba does not, because it fails on the first character. The string aba does not, because it ends in a.

The tool draws this machine and lights up the active states as you feed it characters. Watching the lights is the fastest way to understand why aba fails: the machine reaches the state expecting a final b, reads a, and has nowhere legal to go.

When this view helps and when it does not

Use the automaton view when you want to understand why a pattern accepts or rejects a specific string, or when you want to see nondeterminism as a concrete thing rather than a definition. It is also the right lens for the classic theory result: regexes, NFAs and DFAs describe exactly the same set of languages, called the regular languages.

Do not use this view to reason about the regex features in a programming language such as backreferences, lookahead, or capture groups. Those extensions are strictly more powerful than regular languages and cannot be drawn as a finite automaton at all. The pattern (a+)\1, meaning "some a's repeated," is not regular. Everything on this page assumes the classic operators only: concatenation, union with |, and the Kleene star *.

A finite automaton has a fixed, finite number of states. That is why it cannot count without bound. The language "equal numbers of a's then b's" (a^n b^n) needs unbounded memory and is not regular, no matter how clever the regex looks. If a pattern seems to require counting, it is outside what these machines can do.

From regex to NFA: Thompson's construction

Thompson's construction builds an NFA (nondeterministic finite automaton) from a regex piece by piece. Each operator has a fixed gadget with a start state and one accepting state, glued together with \varepsilon-arrows. An \varepsilon-arrow moves the machine for free, consuming no input.

a \;\Rightarrow\; q_0 \xrightarrow{\,a\,} q_1

A single symbol a becomes two states joined by one arrow labeled a. Here q_0 is the start and q_1 accepts.

R \mid S \;\Rightarrow\; q_s \xrightarrow{\varepsilon} R,\; q_s \xrightarrow{\varepsilon} S,\; R \xrightarrow{\varepsilon} q_f,\; S \xrightarrow{\varepsilon} q_f

Union adds a new start q_s that branches by \varepsilon into both sub-machines R and S, and a new accept q_f that both feed into. The branch is where nondeterminism enters: the machine tries both paths at once.

R^{*} \;\Rightarrow\; q_s \xrightarrow{\varepsilon} R,\; R \xrightarrow{\varepsilon} R,\; q_s \xrightarrow{\varepsilon} q_f

The star wraps R with a loop-back \varepsilon-arrow so it can repeat, plus a skip arrow so it can be used zero times. The count of states stays small: Thompson's construction produces at most 2 states per symbol of the regex. The pattern a(a|b)*b has 5 symbols, so the NFA has at most 10 states.

From NFA to DFA: the subset construction

An NFA can sit in several states at once. To turn it into a deterministic machine (a DFA), track the set of NFA states you could be in. Each such set becomes one DFA state. This is the subset construction.

Start from the set reachable by \varepsilon-arrows from the NFA start. For each input symbol, compute the new set: take every arrow labeled that symbol out of the current set, then close under \varepsilon-arrows again. A DFA state is accepting if its set contains any NFA accepting state.

The number of DFA states can grow, in the worst case, to 2^{n} for an n-state NFA, since every subset is possible. In practice most patterns produce far fewer. For a(a|b)*b the DFA needs only 3 useful states plus a dead state, as the worked example shows.

Worked example: running a(a|b)*b on the string aabb

Use the default pattern a(a|b)*b and the input aabb. Here is the DFA built by the subset construction. Call the states S (start), A (seen the opening a, now in the loop), F (just read a b that could be the final one), and D (dead, no accept possible).

DFA transition table for a(a|b)*b
Stateon aon baccepting?
SADno
AAFno
FAFyes
DDDno
  1. Start in S.
  2. Read a: S goes to A.
  3. Read a: A stays in A (the loop).
  4. Read b: A goes to F.
  5. Read b: F stays in F.
  6. Input exhausted in F, which accepts. The string aabb is accepted.

Now try aba on the same table: S to A on a, A to F on b, then F to A on a. You end in A, which does not accept, so aba is rejected. The final a is the problem, exactly as the plain-English reading predicted.

Without JavaScript, use the transition table above: start in S, follow the column for each character of your string, and check whether the final state is accepting (only F accepts).

Reading the colors and the active set

Three signals matter as you step through a string. First, the number of lit states. If several light up at once, the NFA is exploring multiple paths in parallel: that is nondeterminism made visible. Second, the moment all lights go dark except a dead state. That means no path can recover, and the string will be rejected no matter what follows. Third, the final color: green if the active set contains an accepting state when input runs out, red otherwise.

For (a|b)* nearly every state stays lit throughout, because that pattern accepts everything over the alphabet, including the empty string. For a(a|b)*b the active set is narrow: you stay on a single spine. Comparing the two side by side is the clearest way to feel what the N in NFA buys you.

The active set jumps from 2 to 4 once inside the star loop, then holds steady. Even a deterministic-feeling pattern keeps several NFA states live because of the epsilon-arrows around the star.

Common mistakes

The most frequent error is reading | too greedily. In a|b*, the star binds tighter than the union, so this means "a single a, or any number of b's," not "(a or b) repeated." Write (a|b)* if you mean the latter. The parentheses change the language completely: a|b* accepts a and bbb but rejects ab, while (a|b)* accepts all three.

The second mistake is forgetting the empty string. The star allows zero copies, so (a|b)* accepts the empty input, and its start state is already accepting. If your machine rejects an empty string that you expected to accept, check whether a required symbol sits outside every star.

The third mistake is expecting anchoring. These machines match the entire input, start to finish. There is no partial match and no "contains" semantics unless you write it, for example by wrapping the target in (a|b)* on both sides.

Related tools

If finite machines interest you, step up in power with the Turing Machine Simulator, which adds an unbounded tape and can do the counting an automaton cannot. To see how states and arrows behave as a general structure, build one by hand in the Graph Algorithm Playground. For a different flavor of state exploration, the Pathfinding Visualizer shows search frontiers spreading much like an NFA's active set. And to feel how the worst-case 2^{n} blow-up of the subset construction compares to gentler growth, race it in the Big-O Complexity Race.

Frequently asked questions

Why does one regex light up many states at once?

Because the NFA is nondeterministic. Union and star create branches, so the machine follows every possible path in parallel. The lit states are exactly the set of NFA states you could legally be in after the characters read so far.

Are NFA and DFA equally powerful?

Yes, for the classic operators. The subset construction converts any NFA to a DFA that accepts the same language. The DFA may have more states, up to 2^{n} in the worst case, but it recognizes exactly the same strings.

Why is a^n b^n not expressible here?

Matching equal counts of a's and b's requires remembering how many a's you saw, an unbounded number. A finite automaton has finitely many states and cannot store an unbounded count, so no regex over these operators describes that language.

Does the empty string count as input?

Yes. It is a valid string of length 0. A pattern like (a|b)* accepts it, while a(a|b)*b rejects it because at least two characters are required.

What is an epsilon-arrow?

An arrow the machine can follow without reading any input. Thompson's construction uses them to glue gadgets together. The active set always includes everything reachable by epsilon-arrows from the states you can reach.