Algorithm to Compute FIRST and FOLLOW Sets for a Given Grammar
Computing FIRST and FOLLOW is a foundational step for constructing LL(1) parsing tables. The core idea is to apply grammar production rules that propagate terminals through nullable derivations until the sets reach a fixed point (no more changes). This section provides clear algorithm rules and then presents a complete algorithmic procedure (with pseudocode) for computing FIRST and FOLLOW for an arbitrary context-free grammar, including grammars with (epsilon) productions.
We will rely on the standard fixed-point rules for:
- Nullable (whether ),
- FIRST propagation,
- FOLLOW propagation.
A typical workflow is:
- compute FIRST (and nullability) for all symbols,
- compute FOLLOW by scanning productions and propagating lookahead using FIRST of suffixes and FOLLOW of left-hand sides when the suffix is nullable.
Mermaid overview of dependency flow:
Key terminals/nonterminals notions:
- is a special “empty string” token (not a terminal).
- Let be the start symbol. The end-marker [\]{def="Input end marker used for FOLLOW(start) initialization"} is placed in FOLLOW().
Important: The algorithms below are “worklist/fixed-point” algorithms: they repeatedly apply inference rules until no set changes.
FIRST and FOLLOW Sets (LL(1) Parsing) - Algorithm Overview
Step 1 — Define notation and inference rules
- 1Step 1
Let grammar productions be where is a nonterminal and is a string of terminals/nonterminals (possibly empty). Define FIRST for symbols (terminals/nonterminals) and FOLLOW for nonterminals.
- 2Step 2
For any terminal , define FIRST() = {}. For epsilon, FIRST() = {}.
- 3Step 3
A nonterminal is nullable if . We will compute this implicitly via FIRST containing .
- 4Step 4
If , then FIRST() includes FIRST(){{}}. If is nullable, then also include FIRST(){{}}, and so on. If all are nullable, include in FIRST().
- 5Step 5
Put [\]SA$) = empty set.
- 6Step 6
For any production (where is a nonterminal occurrence in the RHS and are strings): add FIRST(){{}} to FOLLOW(). If can derive (i.e., is nullable), then add FOLLOW() to FOLLOW().
keyword FIRST/FOLLOW are computed by fixed-point iteration.
A Fixed-point approach guarantees termination because each set only grows (monotonic). Each update adds terminals (or for FIRST) and never removes elements; since there are finitely many grammar symbols/terminals, convergence occurs.
We can implement “apply inference rules” using:
- either repeated full passes over productions (“while changed”),
- or a worklist keyed by which FIRST/FOLLOW sets changed.
Below, we present a clear version using repeated passes, which is easiest to understand and still efficient for typical course-scale grammars.
Step 2 — Compute FIRST sets (with epsilon)
- 1Step 1
For each nonterminal : set FIRST() = empty. For each terminal : FIRST() = {}. Set FIRST() = {}.
- 2Step 2
Repeat until no changes: for every production do:
- For ..: add FIRST(){{}} into FIRST().
- Stop early once you reach a non-nullable .
- If every is nullable, add to FIRST().
- 3Step 3
To support FOLLOW later, define a helper FIRSTstring(): compute FIRST of a sequence using the same logic (propagate terminals while nullable continues; include only if all are nullable).
- 4Step 4
Stop when a complete pass over all productions makes no FIRST additions.
Step 3 — Compute FOLLOW sets (using FIRST of suffixes)
- 1Step 1
For the start symbol , set FOLLOW() = {[\]}. For every other nonterminal AA$) = empty.
- 2Step 2
Repeat until no changes: for each production (scan each RHS occurrence of a nonterminal ):
- Add FIRSTstring(){{}} to FOLLOW().
- If is nullable (i.e., FIRSTstring() contains ), then add FOLLOW() to FOLLOW().
- 3Step 3
Stop when a complete pass produces no FOLLOW additions.
Complete pseudocode (end-to-end)
Helper functions
- FIRSTset: FIRST for symbols/nonterminals.
- Nullable: nullable if FIRST contains epsilon.
- FIRSTstring: FIRST of .
1INPUT: 2 Grammar G = (V, T, P, S) 3 Productions P: each is A -> α 4 5OUTPUT: 6 FIRST(X) for all symbols X (terminals, nonterminals, ε) 7 FOLLOW(A) for all nonterminals A 8 9ALGORITHM: 10 111) Initialize FIRST 12 For each terminal a in T: 13 FIRST(a) = {a} 14 FIRST(ε) = {ε} 15 For each nonterminal A in V: 16 FIRST(A) = ∅ 17 182) Compute FIRST (fixed point) 19 repeat 20 changed = false 21 for each production A -> X1 X2 ... Xk in P: 22 // compute FIRST contribution of the RHS sequence 23 add_epsilon = true 24 for i in 1..k: 25 beforeSize = |FIRST(A)| 26 FIRST(A) = FIRST(A) ∪ (FIRST(Xi) \ {ε}) 27 if |FIRST(A)| > beforeSize: changed = true 28 29 if ε not in FIRST(Xi): 30 add_epsilon = false 31 break 32 33 if add_epsilon: 34 beforeSize = |FIRST(A)| 35 FIRST(A) = FIRST(A) ∪ {ε} 36 if |FIRST(A)| > beforeSize: changed = true 37 until not changed 38 393) Helper: FIRSTstring(β) 40 function FIRSTstring(β = Y1...Ym): 41 result = ∅ 42 for j in 1..m: 43 result = result ∪ (FIRST(Yj) \ {ε}) 44 if ε not in FIRST(Yj): return result 45 // if all nullable: 46 return result ∪ {ε} 47 484) Initialize FOLLOW 49 For each nonterminal A in V: 50 FOLLOW(A) = ∅ 51 FOLLOW(S) = FOLLOW(S) ∪ {$} 52 535) Compute FOLLOW (fixed point) 54 repeat 55 changed = false 56 for each production B -> α in P: 57 Let α positions be α1 α2 ... αn (each αi is terminal or nonterminal) 58 for each index i such that αi is a nonterminal A: 59 β = α(i+1) ... αn // suffix after A 60 firstBeta = FIRSTstring(β) 61 62 beforeSize = |FOLLOW(A)| 63 FOLLOW(A) = FOLLOW(A) ∪ (firstBeta \ {ε}) 64 if |FOLLOW(A)| > beforeSize: changed = true 65 66 if ε in firstBeta: 67 beforeSize = |FOLLOW(A)| 68 FOLLOW(A) = FOLLOW(A) ∪ FOLLOW(B) 69 if |FOLLOW(A)| > beforeSize: changed = true 70 until not changed 71 72return FIRST, FOLLOW
Where each rule “feeds” FIRST vs FOLLOW
Conceptual mapping of inference sources.
Common edge cases and correctness notes
Pro Tip
Implement FIRSTstring(β) as a function that scans the sequence left-to-right and stops at the first non-nullable symbol; this matches the definition and keeps FOLLOW propagation correct.
Pitfall: confusing ε with the empty string in FOLLOW
In FOLLOW, you should add FIRST(β){ε} to FOLLOW(A), not ε itself. ε is a marker for 'nullable derivation' and only determines whether to add FOLLOW(B).
Roadmap: FIRST/FOLLOW to LL(1) parsing table
Compute FIRST sets
Step AUse production-based propagation; include ε when all RHS symbols are nullable."
Compute FOLLOW sets
Step BInitialize FOLLOW(start) with $; propagate via FIRST of suffixes and LHS FOLLOW when suffix nullable."
Build LL(1) table
Step CFor each production A->α: use FIRST(α) to fill entries; if α⇒*ε use FOLLOW(A)."
Parse test strings
Step DPredictive parsing with 1-token lookahead; ensure no table conflicts for LL(1)."
Knowledge Check
In the algorithm for FOLLOW sets, what do you add to FOLLOW(A) from a production B -> α A β?