Yasir Explains/Compiler Design/Automata/Deterministic Finite-State Automata (DFA)
Automata

Deterministic Finite-State Automata (DFA)

On this page

Formal Definition of a DFAExample 1 — DFA for Even Number of 0sExample 2 — DFA for Strings Ending in 'ab'DFAs in Lexical Analysis
Automata

Deterministic Finite-State Automata (DFA)

A DFA has exactly one transition per state per symbol — no guessing, no ambiguity. Learn its formal definition, build transition tables and diagrams, and trace acceptance on real examples.

Formal Definition of a DFA

A Deterministic Finite Automaton (DFA) is a 5-tuple:

Example
M = ( Q, Σ, δ, q₀, F )
ComponentDescription
QFinite, non-empty set of states
ΣFinite input alphabet
δ : Q × Σ → QTotal transition function — maps every (state, symbol) pair to exactly ONE next state
q₀ ∈ QStart state
F ⊆ QSet of accepting (final) states

The word deterministic means: for every state q and every symbol a, there is exactly one defined next state δ(q, a). No ambiguity. No choices.


Key Property — Totality

The transition function δ must be total: it is defined for every pair (q, a) where q ∈ Q and a ∈ Σ. If a transition is "missing" in a diagram, it implicitly goes to a special dead state (a non-accepting state that loops to itself on every symbol).

Example
Dead state d: δ(d, a) = d for all a ∈ Σ

This ensures the DFA never "gets stuck" — it always processes the entire input.

Example 1 — DFA for Even Number of 0s

Language: L = { w ∈ {0,1}* | w contains an even number of 0s }

Examples accepted: ε, 1, 11, 00, 010, 0110, 1001, 000011, … (Zero itself is an even count, so ε is accepted)


States

We need to track the parity of 0s seen so far:

  • q0 — even number of 0s seen so far (start + accepting state)
  • q1 — odd number of 0s seen so far

Transition Diagram


Transition Table

Example
| 0 | 1
───────────────────────
→ * q0 | q1 | q0
q1 | q0 | q1

(→ = start state, * = accepting state)


Step-by-Step Traces

Trace 1 — "0110" (should be accepted: two 0s → even)

Example
Start: q0
Read '0': q0 → q1 (0 count becomes 1, odd)
Read '1': q1 → q1 (no change — 1 doesn't affect parity)
Read '1': q1 → q1 (same)
Read '0': q1 → q0 (0 count becomes 2, even)
End state: q0 ✓ ACCEPTED

Trace 2 — "010" (should be rejected: one 0 → odd)

Example
Start: q0
Read '0': q0 → q1
Read '1': q1 → q1
Read '0': q1 → q0 ← wait, that's even

Wait — "010" has two 0s (positions 0 and 2), so it IS accepted. Let's try "0" instead.

Trace on "0" (one zero → odd → rejected):

Example
Start: q0
Read '0': q0 → q1
End state: q1 ✗ REJECTED

Trace on "0011" (two zeros → even → accepted):

Example
Start: q0
Read '0': q0 → q1
Read '0': q1 → q0
Read '1': q0 → q0
Read '1': q0 → q0
End state: q0 ✓ ACCEPTED

Example 2 — DFA for Strings Ending in 'ab'

Language: L = { w ∈ {a,b}* | w ends with 'ab' }

Examples accepted: ab, aab, bab, aaab, abab, baab, …


States

We track the suffix seen so far relative to the pattern 'ab':

  • q0 — no progress toward 'ab' (start state)
  • q1 — have just seen 'a' (one step toward 'ab')
  • q2 — have just seen 'ab' (accepting state)

Transition Diagram


Transition Table

Example
| a | b
───────────────────────
→ q0 | q1 | q0
q1 | q1 | q2
* q2 | q1 | q0

Understanding the Transitions

StateOn 'a'WhyOn 'b'Why
q0→ q1'a' starts the prefix of 'ab'→ q0'b' resets progress
q1→ q1another 'a' restarts the 'a' prefix→ q2'ab' complete!
q2→ q1after 'ab', a new 'a' starts fresh→ q0'abb' — 'b' after pattern, reset

Step-by-Step Traces

Trace 1 — "bab" (accepted)

Example
Start: q0
Read 'b': q0 → q0 (b resets)
Read 'a': q0 → q1 (a starts prefix)
Read 'b': q1 → q2 (ab complete!)
End state: q2 ✓ ACCEPTED

Trace 2 — "abab" (accepted — ends in ab)

Example
Start: q0
Read 'a': q0 → q1
Read 'b': q1 → q2
Read 'a': q2 → q1 (start new attempt)
Read 'b': q1 → q2
End state: q2 ✓ ACCEPTED

Trace 3 — "aba" (rejected — ends in a, not ab)

Example
Start: q0
Read 'a': q0 → q1
Read 'b': q1 → q2
Read 'a': q2 → q1
End state: q1 ✗ REJECTED

DFAs in Lexical Analysis

In a real compiler, the lexer is implemented as a DFA that recognises multiple token types simultaneously. The DFA is built by:

  1. Writing a regular expression for each token type.
  2. Converting each RE to an NFA (Thompson's construction).
  3. Merging all NFAs (union) into one combined NFA.
  4. Converting the combined NFA to a DFA (subset construction).
  5. Minimising the DFA.

The resulting DFA is huge but runs in O(n) — one state transition per input character.


How the Lexer DFA Works

Example
Current character position: i = 0
Current DFA state: start
Loop:
Read character c at position i
Move to δ(current_state, c)
If new state is a dead state:
backtrack to last accepting state position
emit the token found there
reset DFA to start state
continue from backtrack position
Else:
advance i by 1
When all input consumed:
If current state is accepting → emit final token
Else → lexical error

This is why lexers are so fast — no backtracking, no recursion, just a table lookup per character.