Worked examples — NFA to DFA conversion — subset construction
Before starting, make sure these are solid: Finite Automata — NFA and ε-NFA (what an NFA and an ε-move are), Finite Automata — DFA basics (what a single-state machine is), and Power Set and Subsets (why there are subsets of an -element set).
The scenario matrix
Every subset-construction problem is really one of these case classes. If we work an example of each, you will never meet a cell you haven't seen.
| # | Case class | What's tricky about it | Covered by |
|---|---|---|---|
| C1 | No ε-edges (plain NFA) | ECLOSE does nothing — pure MOVE | Example 1 |
| C2 | With ε-edges (chained / ε-cycle) | must close before start and after every move; closures can iterate | Example 2 |
| C3 | Dead / trap state appears () | a MOVE returns the empty set | Example 3 |
| C4 | Start state is itself accepting | the empty string must be accepted | Example 4 |
| C5 | Blow-up: near reachable subsets | many subsets actually reachable | Example 5 |
| C6 | Degenerate: no final state at all | language is empty — every subset rejects | Example 6 |
| C7 | Word problem (real-world spec) | translate English → NFA → DFA | Example 7 |
| C8 | Exam twist: unreachable subsets | don't list all ; only reachable ones | Example 8 |
Notation reminder, so nothing is used before it's earned:
- A subset-state is written — literally the set of NFA states the NFA might be sitting in. This is one DFA state.
- = union of all -arrows leaving the states in .
- = plus everything reachable by following -arrows only.
- means " shares at least one state with the final set " — that "" is set intersection, and "" means "the overlap is not empty".
Throughout, the pictures carry the work: magenta circles are non-accepting DFA states, violet double circles are accepting, orange is the dead/trap state, and the orange start arrow marks the start. Watch the arrows, not just the tables.
Example 1 — Case C1 (no ε)
Step 1 — start state. . Call it . Why this step? Because there are no ε-edges, ECLOSE cannot add anything — for case C1 the start is just .
Step 2 — on . . Why? Only state in the set is ; its -arrow returns . No ε ⇒ ECLOSE unchanged. Self-loop.
Step 3 — on . . New subset — call it . Why? 's -arrow is . This subset hasn't appeared, so it's a brand-new DFA state.
Step 4 — on . . Self-loop.
Step 5 — on . . Self-loop.
Step 6 — accepting? → no. → yes.
The finished DFA — look at how the orange start arrow enters , and how every arrow out of curls back to (once you have seen a you can never lose it):

| DFA state | on 0 | on 1 | accept? |
|---|---|---|---|
| (start) | no | ||
| yes |
Verify: we reach exactly once we've read a , and once in we stay there. So the DFA accepts every string containing at least one . Trace "": , reject (correct — no 1). Trace "": , accept. ✔ Only 2 of subsets reachable.
Example 2 — Case C2 (ε-edges, with a chained ε-closure and an ε-cycle)
Step 1 — start (chained closure, needs several iterations). :
- Iteration 1: from , ε-edge to → set now .
- Iteration 2: from the newly added , ε-edge to → set now .
- Iteration 3: from the newly added , no ε-edge → nothing new, stop.
Start . Why iterate? ECLOSE is a fixed-point computation: after adding you must re-scan 's ε-edges, which reveal . A single pass that only looked at would wrongly stop at . This is the chained-ε pitfall the parent note only hinted at.
Step 2 — on . (only has an -arrow). Then : from , ε-edge to → ; from , no ε → stop. New state . Why does the ε-cycle not loop forever? The set already contains both and ; ECLOSE only adds states, so once nothing new can be added it halts — even though visually cycles.
Step 3 — on . . ECLOSE. Dead state — call it . (Foreshadows C3.) Why is MOVE empty here? We scan every state in for a -arrow: state has none, state has none, state has none. The union of three empty sets is empty — so there is literally nowhere to go on .
Step 4 — on . : 's -arrow is , has no -arrow → . ECLOSE. Self-loop.
Step 5 — on . : has no -arrow, 's -arrow is → . ECLOSE. Self-loop.
Step 6 — accepting? contains → yes. has no → no. → no.
The DFA — note the orange dead state hanging off on , and looping to itself on both letters:

| DFA state | on | on | accept? |
|---|---|---|---|
| (start) | no | ||
| yes | |||
| (dead) | no |
Verify: to accept, we must first read an (moving ); after that any mix of 's and 's keeps us in . So the language is " followed by any string over " = . Trace "": (accept). Trace "": (accept). Trace "": (reject). Trace "": (reject). ✔ And the start subset really needed three closure iterations to build.
Example 3 — Case C3 (dead/trap state)
Step 1 — start. (no ε).
Step 2 — on . .
Step 3 — on . . This is the empty subset — the DFA's dead state . Why this step matters? The NFA has no live states after , so it can never reach a final state again. In a DFA every transition must be defined, so we make a real state that loops to itself forever (Mistake 5 from parent).
Step 4 — on . Self-loop, never accepts.
The trap is easiest to see — once the orange dead state is entered, every arrow curls back into it:

| DFA state | on | accept? |
|---|---|---|
| (start) | no | |
| yes | ||
| (dead) | no |
Verify: language is exactly the single string "". Trace "": , accept. Trace "": , reject forever. Trace "" : stays , reject. ✔ This DFA recognises — one string only.
Example 4 — Case C4 (start is accepting, accepts )
Step 1 — start. .
Step 2 — is the START accepting? → yes, is accepting. Why this step is easy to miss? People check accepting states after building transitions and forget the start can already be final — that is exactly how you accept the empty string (the DFA halts on having read nothing).
Step 3 — on . .
Step 4 — on . .
| DFA state | on | accept? |
|---|---|---|
| (start) | yes | |
| no |
Verify: language is "even number of 's" (including zero) = . Trace "" : at , accept. Trace "": , reject. Trace "": , accept. ✔
Example 5 — Case C5 (near blow-up)
Step 1 — start. (no ε).
Step 2 — on : . on : . Why is new? The -arrow of is nondeterministic (), giving a genuinely bigger subset.
Step 3 — on : . on : .
Step 4 — on : . on : . Why does contribute nothing? has no outgoing arrows — it's a sink in the NFA.
Step 5 — on : . on : .
| DFA state | on | on | accept? |
|---|---|---|---|
| (start) | no | ||
| no | |||
| yes | |||
| yes |
Verify: both contain → accepting. Language: strings whose 2nd-last symbol is . Trace "": (accept — 2nd-last is ). Trace "": (reject — 2nd-last is ). Trace "": (accept). ✔ 4 of 8 subsets reachable — for the general "-th from end" NFA, all become reachable, the true exponential blow-up.
Example 6 — Case C6 (no final state → empty language)
Step 1 — start. .
Step 2 — transitions: on ; on .
Step 3 — accepting? For any subset , . Why this settles it? Intersecting with the empty set is always empty, so no subset can be accepting — no matter how the transitions look.
| DFA state | on | accept? |
|---|---|---|
| (start) | no | |
| no |
Verify: language (the empty language — not the same as !). Every string is rejected because no DFA state is accepting. ✔ This is the mechanical reason "no final states ⇒ empty language" — the subset construction reproduces it automatically.
Example 7 — Case C7 (word problem)
Step 0 — build the NFA (why an NFA first?). NFAs let us "guess" when the crucial occurs, which is far easier than tracking it deterministically. States , start , final : , , , , . (In , reading either ignores it or "commits" to it via ; from a later unlocks to , which then loops forever.)
Step 1 — start. .
Step 2 — on : . on : . Why? Before any , a is useless — stay at .
Step 3 — on : . on : .
Step 4 — on : . on : .
Step 5 — on : . on : .
Step 6 — accepting? Contains : and → yes. → no.
| DFA state | on | on | accept? |
|---|---|---|---|
| (start) | no | ||
| no | |||
| yes | |||
| yes |
Verify: once we hit or (unlocked), every arrow keeps us among — so it stays unlocked, matching the forecast. Trace "": (unlock). Trace "": (still locked — the came before ). Trace "": (unlocked). ✔
Example 8 — Case C8 (exam twist: don't enumerate all )
Step 1 — start. (no ε).
Step 2 — reachability only. on : ; on : . Why only reachable? Mistake 3 from the parent note: unreachable subsets are dead weight. We generate a subset only when an arrow lands on it.
Step 3 — process . on : ; on : .
Step 4 — process . on : ; on : . Both self-loops.
Step 5 — process . on . Dead state.
Step 6 — accepting? contains → yes. → no.
| DFA state | on | on | accept? |
|---|---|---|---|
| (start) | no | ||
| no | |||
| yes | |||
| (dead) | no |
Verify: reachable subsets = 4 of 8. The 4 unreachable subsets (, , , ) never occur because this NFA's arrows are single-valued (no nondeterministic branching), so no subset ever grows past size 1. Language: = "starts with ". Trace "": (accept). Trace "": (reject). Trace "": (accept). ✔
Recall Self-test: name the case class
Which cell does an NFA with no final states hit, and what's the resulting language? ::: Case C6 — the empty language ; every subset rejects because . Which cell forces you to accept the empty string ? ::: Case C4 — the start subset already contains a final state. When does the dead state get created? ::: Case C3 — whenever a MOVE returns the empty set; it self-loops and rejects. Why did Example 2's start subset need three closure iterations? ::: The ε-edges chain ; you must re-scan each newly added state, so a single pass would wrongly stop at . Why did Example 5 reach 4 subsets while Example 8's subsets never grew past size 1? ::: Example 5's NFA branches nondeterministically (subsets grow); Example 8's arrows are single-valued, so subsets stay singletons plus the dead state.
Connections
- Parent: subset construction
- Finite Automata — DFA basics
- Finite Automata — NFA and ε-NFA
- Power Set and Subsets
- Regular Expressions to NFA — Thompson's construction
- DFA Minimization — Myhill–Nerode
- Closure Properties of Regular Languages
- Pumping Lemma for Regular Languages