Draft
Nondeterministic Finite Automata
Recognize patterns by letting a finite-state machine branch, keeping any path that could still succeed.
Hook problem
We want to recognize binary strings that contain the substring 01. The string 010 should be accepted because the first two symbols are 01. The string 1110 should be rejected because no 0 is immediately followed by 1.
The interesting part is not that this language is hard. It is deliberately small. The goal is to compare two ways of telling the same finite-memory story.
pattern found
no 01 yet
pattern found
DFA baseline
A DFA can solve this with one current state. It can remember whether the previous symbol was a promising 0, and once it has seen 01, it can stay accepting forever.
That deterministic view works. An NFA gives an alternate viewpoint: at every 0, keep scanning normally and also start a branch that treats this 0 as a possible beginning of 01.
One current state remembers whether the previous symbol was a promising 0.
current: saw last 0The same prefix can keep q0 alive and also try q1 as a candidate start.
The branching idea
The smallest new invention is that a transition may return several next states. From q0 on input 0, the machine moves to both q0 and q1:
One branch says “keep looking.” The other branch says “try this 0 as the start of 01.”
One transition can return a set: delta(q0, 0) = {q0,q1}.
What each state means
This NFA has three states:
q0: scanning for a possible start.q1: just chose a0as the possible start of01.q2: already saw01; accept and stay accepting.
These are not three separate machines. They are three possible histories that can be active after reading the same prefix.
This branch has not committed to a particular 0 yet; it keeps scanning the whole suffix.
This branch just chose the latest 0 as a possible start of the substring 01.
This branch has already seen 01, so any remaining symbols keep it accepting.
Branch death
Missing transitions matter. From q1, reading 1 completes the pattern and goes to q2; reading 0 does not complete 01, so that old candidate branch has nowhere to go:
For input 00, after the first 0 the active set is {q0,q1}. On the second 0, old q1 dies, but old q0 moves to {q0,q1} and immediately spawns a fresh candidate for the second 0. The active set remains {q0,q1}, but the event ledger tells the real story.
| Old branch | Event | Result |
|---|---|---|
| q0 | q0 --0--> {q0,q1} | old q0 keeps scanning in q0 and also spawns q1 for this 0. |
| q1 | q1 --0--> {} | q1 has no transition on 0, so that old branch dies. |
Before: {q0,q1}. After: {q0,q1}. The set looks unchanged, but the ledger shows one old q1 died and a new q1 was spawned.
Active state set
Instead of drawing every branch separately, we summarize all live branches with an active state set. For input 010, the exact trace is:
{q0} -> {q0,q1} -> {q0,q2} -> {q0,q1,q2}
The row after prefix 01 would accept if the input ended there, because q2 is active. The whole input is accepted only after every symbol has been consumed.
| Prefix | Read | Active states | Would accept if ended here? | Explanation |
|---|---|---|---|---|
| ε | - | {q0} | no | Start with only q0 active: scan for a possible 0 that could begin 01. |
| 0 | 0 | {q0,q1} | no | Reading 0 keeps q0 alive and starts a candidate q1 branch for this 0. |
| 01 | 1 | {q0,q2} | yes | A q1 branch reads 1 and reaches accepting q2; q0 still keeps scanning. |
| 010 | 0 | {q0,q1,q2} | yes | Reading 0 keeps q0 alive and starts a candidate q1 branch for this 0. |
Interactive visual demo
Step through 010, 00, and 1010. Use the active-set view to see the compact summary, then switch to branch-events view to see exactly which old branch created or lost each state.
Would reject if input ended here.
- Active states
- {q0}
- Consumed prefix
- ε
- Remaining input
- 010
- Next symbol
- 0
Start with only q0 active: scan for a possible 0 that could begin 01.
| Now | Prefix | Read | Active states | Would accept if input ended here? |
|---|---|---|---|---|
| now | ε | - | {q0} | no |
| 0 | 0 | {q0,q1} | no | |
| 01 | 1 | {q0,q2} | yes | |
| 010 | 0 | {q0,q1,q2} | yes |
| State | 0 | 1 |
|---|---|---|
| q0 | {q0,q1} | {q0} |
| q1 | {} | {q2} |
| q2 | {q2} | {q2} |
Formal version
An epsilon-free nondeterministic finite automaton is a 5-tuple:
Here Q is the finite set of states, \Sigma is the input alphabet, q0 is the start state, and F is the set of accepting states.
The key difference from a DFA is the transition function:
P(Q) means the power set of Q: the set of all subsets of states. A transition returns a set of possible next states, not one required next state.
For the contains-01 NFA, Q = { q0, q1, q2 }, \Sigma = { 0, 1 }, q0 = q0, F = { q2 }, and the transition table is the one shown here.
{ q0, q1, q2 }
{ 0, 1 }
Q x Σ -> P(Q)
q0
{ q2 }
| State | 0 | 1 |
|---|---|---|
| q0 | {q0,q1} | {q0} |
| q1 | {} | {q2} |
| q2 | {q2} | {q2} |
Epsilon note
Some NFA definitions also allow epsilon transitions. An epsilon transition consumes no input; it is written with .
This node does not use them. The main implementation stays epsilon-free with . Epsilon-NFAs extend the transition domain to include and need epsilon-closure bookkeeping, which is a useful later topic but not needed for the branching semantics here.
An epsilon transition consumes no input. This node's main machine has none, so delta stays Q x Sigma -> P(Q).
Implementation sketch
The direct simulation stores a set of active states. For each input symbol, ask every currently active state where it can go, then union all answers:
For 010, after reading prefix 0, S_1 = {q0,q1}. The next symbol is 1, so:
function accepts(input: string) {
let activeStates = new Set<NfaState>(["q0"]);
for (const symbol of input) {
const nextActiveStates = new Set<NfaState>();
for (const state of activeStates) {
for (const next of transitionTable[state][symbol]) {
nextActiveStates.add(next);
}
}
activeStates = nextActiveStates;
}
return activeStates.has("q2");
}
let next = new Set<NfaState>();
for (const q of activeStates) {
for (const r of delta[q][symbol]) {
next.add(r);
}
}From S1 = {q0,q1} on 1:
{q0} union {q2} = {q0,q2}
Prefix invariant
After consuming the first i symbols, S_i contains exactly the states reachable by some branch that reads that prefix. No branch is invented later, and no live branch is forgotten.
That invariant explains the acceptance rule:
In words: after the whole input is consumed, accept if at least one active branch is in an accepting state.
After prefix 01, S2 = {q0,q2}. Because q2 is active, this prefix would accept if the input ended here.
Complexity
For input length n, direct simulation examines the active states for each symbol. There are at most |Q| active states, so the running time is with a table lookup representation. The transition table has |Q||Σ| rows or cells of sets.
A future subset-construction node will show how active sets can be compiled into DFA states. This page stops at direct NFA simulation.
input symbols
maximum active states scanned per step
transition-table cells
direct simulation time for a fixed alphabet lookup table
Common confusions
A dead branch is not a rejected input. For 00, the old q1 candidate dies, but the q0 branch continues, so the run is still meaningful.
Also, acceptedIfInputEndedHere is not the same field as whole-input accepted. A trace row can say “would accept if the input ended here”; the final answer is checked only at the last row.
In 00, old q1 dies, but old q0 keeps the run alive.
A trace row says would accept if input ended here; whole-input accepted is checked only at the final row.
Connections in the graph
DFA is the prerequisite: learn the one-current-state semantics first, then NFA branching is a controlled generalization where one prefix can have a set of possible current states.
Future nodes can connect NFA to subset construction, regular expressions, and epsilon-NFA construction. Those edges are not added until those nodes exist.
Exercises
- Predict the active sets for
01. - For
00, which old branch dies on the second symbol? - Why does
1010accept even though it starts with1? - What changes in the implementation if epsilon transitions are allowed?
Predict the final active set and result.
Reveal answer
{q0,q2} - accepted
Predict the final active set and result.
Reveal answer
{q0,q1} - rejected
Predict the final active set and result.
Reveal answer
{q0,q1,q2} - accepted
Predict the final active set and result.
Reveal answer
{q0} - rejected
Graph connections : Nondeterministic Finite Automata