A step-by-step guide to understanding NFAs, Subset Construction, and epsilon-transitions
Based on CS154/CS305 lecture materials (Ullman)
Use Arrow Keys or the buttons below to navigate. Press S to step through animations.
Where does this lecture fit?
NFAs are much easier to design (fewer states, more flexibility). But computers can only run DFAs (one state at a time). So we need a way to convert NFA -> DFA. That's the Subset Construction.
Before we learn NFAs, let's remember DFAs
A DFA (Deterministic Finite Automaton) has:
A DFA is like following GPS directions. At every intersection, you're told exactly one way to turn. There are no choices -- you just follow the instructions.
The key difference: CHOICE
An NFA (Nondeterministic Finite Automaton) changes two rules:
An NFA accepts a string if ANY sequence of choices leads to a final state. It only needs one successful path.
Imagine you're in a maze with forking paths. A DFA is like a maze where every junction has exactly one door. An NFA is like a maze where junctions can have multiple doors (or even zero doors!).
The NFA "accepts" if there exists some path through the maze from start to exit. It's as if you can clone yourself at every fork and explore all paths at once.
You've been using nondeterminism your whole life
When you type "helo", the spell-checker simultaneously considers "hello", "help", "halo", "hero" -- exploring multiple correction paths at once, just like an NFA.
Each candidate is a parallel "branch" of computation.
Your GPS shows 3 routes simultaneously -- via highway, through downtown, scenic route. An NFA explores all paths; a DFA would commit to just one.
If ANY route reaches the destination, you accept it.
When you run grep "ab*c", it compiles to an NFA internally. The engine explores multiple match positions simultaneously.
That's why regex is fast -- NFA parallelism!
In every example, the system explores multiple possibilities at once and succeeds if any one works out. That's exactly what an NFA does -- parallel exploration with existential acceptance.
It's a design tool, not a physical machine
An NFA lets you describe WHAT to recognize, not HOW to recognize it. You say "the string ends with 01" and the NFA just guesses when the ending starts.
NFAs are often exponentially more compact. An NFA for "n-th symbol from the end is 1" needs ~n states. The equivalent DFA needs ~2n states!
Want L1 ∪ L2? Just add a new start state with epsilon-transitions to both NFAs. Try doing that with DFAs -- it's much harder!
"NFAs are more powerful than DFAs."
FALSE! They recognize exactly the same languages. Nondeterminism gives you design convenience, not extra power. The subset construction proves this.
The "N" in NP stands for Nondeterministic Polynomial time. Same idea, grander scale: "if I could magically guess the right answer, could I verify it quickly?"
For finite automata, nondeterminism doesn't add power. For polynomial time... that's the biggest open question in CS!
Despite looking more powerful, NFAs recognize exactly the same languages as DFAs -- the regular languages. NFAs are just a more convenient notation. We can always convert NFA -> DFA.
The 5-tuple: same components as a DFA, one key difference
An NFA is a 5-tuple (Q, Σ, δ, q0, F):
| Symbol | Meaning | Same as DFA? |
|---|---|---|
| Q | Finite set of states | Yes |
| Σ | Input alphabet | Yes |
| δ | Transition function | DIFFERENT! |
| q0 | Start state (one state) | Yes |
| F | Set of final/accept states | Yes |
DFA: δ(q, a) = one state (a single state p)
NFA: δ(q, a) = a SET of states (like {p, r, s} or even {} )
That's it! The only formal difference is that δ returns a set instead of a single state.
The slides' first example -- let's break it down carefully
Imagine a 3x3 chessboard. The squares alternate in color (like a real chessboard):
From square 5 (center), reading 'r', you could move to 1, 3, 7, or 9. That's four choices -- impossible in a DFA, natural in an NFA!
Where can you go from each square?
"Adjacent" = shares an edge or corner (king's moves in chess).
| State | δ(_, r) | δ(_, b) |
|---|---|---|
| -> 1 | {5} | {2, 4} |
| 2 | {1, 3, 5} | {4, 6} |
| 3 | {5} | {2, 6} |
| 4 | {1, 5, 7} | {2, 8} |
| 5 | {1, 3, 7, 9} | {2, 4, 6, 8} |
| 6 | {3, 5, 9} | {2, 8} |
| 7 | {5} | {4, 8} |
| 8 | {5, 7, 9} | {4, 6} |
| * 9 | {5} | {6, 8} |
-> = start, * = accept
Row "5", column "r" says {1,3,7,9}. This means: "from center square 5, if you move to an adjacent red square, you could go to any corner."
Watch the NFA for "ends with 01" process your string step by step
NFA: accepts strings over {0,1} that end with "01"
How δ works on entire strings, not just single symbols
We need to extend δ(q, a) -- which handles one symbol -- to δ(q, w) -- which handles an entire string w.
Base case: δ(q, ε) = {q}
"Reading the empty string from q, you're still just at q."
Inductive case: δ(q, wa) = ⋃ δ(p, a) for all p in δ(q, w)
"To process string wa: first process w to get a set of states S. Then, from each state in S, follow the transition on symbol a. Union all results."
Think of the NFA as a network of pipes. You pour water in at the start state. At each symbol, water flows through all possible transitions. The water "spreads" to all reachable states simultaneously. At the end, if water reaches any accept state, the string is accepted.
A string w is accepted if δ(q0, w) contains at least one final state.
The language L(N) = { w | δ(q0, w) ∩ F ≠ ∅ }
The most important theorem of this lecture
For every NFA, there exists a DFA that accepts exactly the same language. And vice versa.
Every DFA is already an NFA! Just wrap the single next-state in a set:
This requires the Subset Construction -- the central algorithm of this lecture.
If the NFA has n states, the equivalent DFA can have up to 2n states! (one for each subset of NFA states). In practice it's usually much less, but the worst case is real.
The core insight behind converting NFA to DFA
An NFA can be in a set of states at once. A DFA must be in one state at a time. Solution: make each DFA state represent a set of NFA states!
Each DFA state is named by a set, but it IS a single state in the DFA. Don't confuse the name with multiple states!
Given NFA (Q, Σ, δN, q0, F), build DFA:
| DFA component | How to build it |
|---|---|
| States | 2Q = all subsets of Q |
| Alphabet | Same Σ |
| Start state | {q0} |
| Accept states | Any subset containing a member of F |
| δD(S, a) | Union of δN(q, a) for all q in S |
{p, q} is a single DFA state whose name happens to be a set. Don't think of it as "being in two places."
Let's convert the chessboard NFA to a DFA (lazy construction)
| r | b | |
|---|---|---|
| -> 1 | 5 | 2,4 |
| 2 | 1,3,5 | 4,6 |
| 3 | 5 | 2,6 |
| 4 | 1,5,7 | 2,8 |
| 5 | 1,3,7,9 | 2,4,6,8 |
| 6 | 3,5,9 | 2,8 |
| 7 | 5 | 4,8 |
| 8 | 5,7,9 | 4,6 |
| *9 | 5 | 6,8 |
The final result after subset construction
| DFA State | r | b |
|---|---|---|
| -> {1} | {5} | {2,4} |
| {5} | {1,3,7,9} | {2,4,6,8} |
| {2,4} | {1,3,5,7} | {2,4,6,8} |
| * {1,3,7,9} | {5} | {2,4,6,8} |
| {2,4,6,8} | {1,3,5,7,9} | {2,4,6,8} |
| {1,3,5,7} | {1,3,5,7,9} | {2,4,6,8} |
| * {1,3,5,7,9} | {1,3,5,7,9} | {2,4,6,8} |
* = accept (contains state 9)
We only built states we could actually reach from {1}. The "full" construction would enumerate all 512 subsets -- most would be unreachable waste!
A cleaner example to solidify the idea
On seeing a 0, state q0 has a choice:
It pursues both guesses simultaneously. If the guess was wrong (q1 doesn't see a '1' next), that branch dies. If right, it reaches q2!
A DFA can't "guess." It has to somehow remember it saw a 0 while also continuing to process more input. The subset construction handles this automatically.
Converting the 3-state NFA to a DFA
| 0 | 1 | |
|---|---|---|
| -> q0 | {q0, q1} | {q0} |
| q1 | {} | {q2} |
| * q2 | {} | {} |
| DFA State | 0 | 1 |
|---|---|---|
| -> {q0} | {q0,q1} | {q0} |
| {q0,q1} | {q0,q1} | {q0,q2} |
| * {q0,q2} | {q0,q1} | {q0} |
Only 3 DFA states (out of 23=8 possible). The empty set {} and states like {q1}, {q2}, {q1,q2}, {q0,q1,q2} are all unreachable from {q0}.
Watch the algorithm build a DFA from the "ends with 01" NFA, step by step
| 0 | 1 | |
|---|---|---|
| -> q0 | {q0, q1} | {q0} |
| q1 | {} | {q2} |
| * q2 | {} | {} |
| DFA State | 0 | 1 |
|---|
Proof sketch (by induction on string length)
For any string w: δN(q0, w) = δD({q0}, w)
"The set of NFA states after reading w equals the single DFA state (which is named by that set) after reading w."
δN(q0, ε) = {q0} = δD({q0}, ε)
Both just stay at the start.
Assume it works for x: δN(q0, x) = δD({q0}, x) = S
Then for w = xa:
The DFA state after reading w literally IS the set of all NFA states after reading w. The DFA's transition function was defined to make this true. So of course it works!
Adding "free" transitions that consume no input
An ε-NFA has everything an NFA has, plus transitions on the special symbol ε (epsilon).
An ε-transition lets the automaton move from one state to another without reading any input. It's a "free move" -- spontaneous, no input consumed.
Think of ε-transitions as secret passages in a maze. You can walk through them at any time without using a key (input symbol). They're like hidden shortcuts between rooms.
Solid arrows = real transitions (consume input). Dashed arrows = ε (free moves).
E can teleport to B and C via ε. B can teleport to D.
The transition table now has an extra ε column:
| 0 | 1 | ε | |
|---|---|---|---|
| -> A | {E} | {B} | ∅ |
| B | ∅ | {C} | {D} |
| C | ∅ | {D} | ∅ |
| * D | ∅ | ∅ | ∅ |
| E | {F} | ∅ | {B,C} |
| F | {D} | ∅ | ∅ |
Click a state to watch its ε-closure expand step by step
CL(q) = the set of all states you can reach from q by following zero or more ε-transitions.
Note: q itself is always in CL(q) (zero ε-transitions = stay put).
Regular transitions dimmed. ε-transitions highlighted — these are what closure follows.
Click a state to compute CL(q):
CL(E) isn't just {E, B, C}. Since B has its own ε-transition to D, you must follow that too! CL(E) = {E} ∪ {B,C} ∪ CL(B) ∪ CL(C) = {E, B, C, D}.
Try clicking E to see the chained closure in action.
Processing strings in an ε-NFA means accounting for "free moves" at every step
Base: δ̂(q, ε) = CL(q)
"Before reading anything, you can already be in any state reachable via ε-transitions."
Induction: δ̂(q, xa) is computed:
Eliminating ε-transitions by "baking them in"
Given ε-NFA with transition function δE, build an ordinary NFA with δN:
Same states, same alphabet, same start state.
New transitions: δN(q, a) =
New accept states F': any state q where CL(q) contains a state in F.
(If you can reach an accept state via ε alone, you're accepting too.)
ε-NFA
| 0 | 1 | ε | |
|---|---|---|---|
| ->A | {E} | {B} | ∅ |
| B | ∅ | {C} | {D} |
| C | ∅ | {D} | ∅ |
| *D | ∅ | ∅ | ∅ |
| E | {F} | ∅ | {B,C} |
| F | {D} | ∅ | ∅ |
Ordinary NFA
| 0 | 1 | |
|---|---|---|
| ->A | {E} | {B} |
| *B | ∅ | {C} |
| C | ∅ | {D} |
| *D | ∅ | ∅ |
| *E | {F} | {C,D} |
| F | {D} | ∅ |
What changed?
Test your understanding -- trace the computation!
Fill in the DFA transition table from this NFA
| a | b | |
|---|---|---|
| -> q0 | {q0, q1} | {q0} |
| q1 | {} | {q2} |
| * q2 | {} | {} |
Click each ? cell to select the correct set of states.
A student did subset construction but made a mistake. Can you find it?
| 0 | 1 | |
|---|---|---|
| -> p0 | {p0} | {p0, p1} |
| p1 | {p2} | {} |
| * p2 | {} | {} |
One cell is WRONG. Click it!
Look carefully at state {p0, p1} under input 0. What should δ(p0, 0) ∪ δ(p1, 0) be?
Everything connects
Quick reference for exams