Worked examples — Aho-Corasick — multiple pattern search, automaton
The scenario matrix
Before working examples, let us list every distinct case-class the automaton can hit. Each later example is tagged with the cell it covers so you can see nothing is skipped.
| # | Case class | What is unusual | Covered by |
|---|---|---|---|
| C1 | Degenerate: empty text | text length , machine never leaves root | Ex 1 |
| C2 | Degenerate: single-char patterns | pattern length 1, terminal sits at depth 1 | Ex 1 |
| C3 | Miss at root | text char has no edge from root; | Ex 2 |
| C4 | Nested patterns (one is a prefix of another) | e.g. he inside hers — both report at their own ends |
Ex 3 |
| C5 | Suffix-sharing patterns (dictionary chain) | she ends and he also ends at the SAME position |
Ex 3 |
| C6 | Deep failure jump | mismatch forces a fall-back several levels up | Ex 4 |
| C7 | Multiple matches from one state | one landing triggers 2+ reports via dictionary link | Ex 5 |
| C8 | Overlapping occurrences of the SAME pattern | pattern found again starting inside its previous copy | Ex 6 |
| C9 | Real-world word problem | build a profanity/keyword filter, count hits | Ex 7 |
| C10 | Exam twist: fail vs goto confusion | proves fail is a node equal to a suffix, not a child | Ex 8 |
Every quadrant of "what can go weird" (empty / tiny / miss / nested / shared / deep / multiple / overlapping / applied / trap) is one row above.
Example 1 — Degenerate inputs (C1, C2)
Forecast: guess — how many matches for the empty text? And for ab, where do the reports land?
Steps.
- Build the trie. Root has two children: node
a(terminal fora) and nodeb(terminal forb). Why this step? We must always have the structure before scanning; single-char patterns just make terminals sit at depth 1. - Failure links. Root's children get by the base rule. Why this step? Depth-1 nodes have no proper suffix longer than the empty string, so they fall back to root.
- Scan
"". The loop over characters never runs. State stays at root; root is not terminal. Why this step? An empty text feeds zero characters, so the machine cannot move — 0 matches is forced by the definition. - Scan
ab.- char
a: nodea→ report a (position 0). - char
b: : nodeahas no childb; follow ; root has childb→ nodeb→ report b (position 1).
- char
Verify: Total matches on ab (a at index 0, b at index 1); on "" . Each length-1 pattern matched exactly once — consistent with ab containing one a and one b.
Example 2 — Miss at the root (C3)
Forecast: the leading x matches nothing — does the machine reset cleanly, or get confused?
Steps.
- Trie: root →
c→ca→cat(✓cat). - char
x: root has no childx. Rule: . Why this step? Root is its own failure sink; a char with no edge from root simply keeps us at root — no progress lost because there was none. - char
c: nodec. Why this step? Real edge exists, so we take it. - char
a:c→ca. chart:ca→cat→ report cat (ends at index 3).
Verify: cat occurs in xcat once, ending at index 3 (0-based). The junk prefix x cost us nothing — exactly one match reported. ✓
Example 3 — Nested + suffix-sharing patterns (C4, C5)
Forecast: at which single character do two words fire at once?

Steps.
- Trie strings:
h, he(✓), her, hers(✓), s, sh, she(✓), hi, his(✓). Why this step? We need the tree so failure links have somewhere to point. - Key failure link: longest proper suffix of
shethat is a trie node ishe, so . Why this step? This rope is what lets one landing report two words:heis terminal and sits onshe's dictionary chain. - Scan
u s h e r s(see figure — follow the coloured path):char state reports u root – s s – h sh – e she she, he r her – s hers hers Why the double report at e? Landing onshe(terminal) we walk its dictionary link tohe(also terminal) → both fire. That is cell C5. - Why
rdid not restart at root (C6 preview): nodeshehas no childr; follows , andhedoes have a childr→her. Progress preserved.
Verify: matches on ushers = she (index 1–3), he (index 2–3), hers (index 2–5): total 3 matches. Nesting: he reported inside hers's region, she/he share the endpoint at index 3. ✓
Example 4 — Deep failure jump (C6)
Forecast: after spelling abcd, if the next char were x, how many levels do we drop?

Steps.
- Trie: one long branch
a→ab→abc→abcd(✓), plus branchesb→bc→bcd(✓),c→cd(✓),d(✓). - Failure links (each points to the same suffix one letter shorter):
, , , .
Why this step? Because
bcdis the longest proper suffix ofabcdthat is a node, and so on down. - Scan
abcd: we walk straight down the long branch. At the final nodeabcdwe report abcd, then follow the dictionary chainabcd → bcd → cd → d— all four patterns end here! Why this step? Every shorter pattern is a suffix ending at the same index, so all live on one dictionary chain. - The deep jump. Suppose one more char
xarrives. : no edge; try (nox), thencd(nox), thend(nox), then root (nox) → land at root. We climbed 4 levels in one query step — but this cost is amortised into build time.
Verify: matches on abcd = {abcd, bcd, cd, d}, total 4, all ending at index 3. ✓
Example 5 — Multiple matches from one state via dictionary chain (C7)
Forecast: which single character produces two reports at once?
Steps.
- Trie:
a(✓)→ab(✓),b→bc(✓),c(✓). - Failure links: (longest proper suffix of
abthat is a node isb), (suffixcis a node & terminal). Why this step? is exactly the rope that will chain two matches. - Scan
a b c:a: nodea→ report a.b: nodeab→ report ab.c: :abhas noc; ;bhas childc→ nodebc→ report bc, then dictionary link (terminal) → report c. Why this step? One state (bc) triggers two reports (bc,c) — that is cell C7. Never stop at the terminal flag; always walk the chain.
Verify: matches on abc = a(idx0), ab(idx0–1), bc(idx1–2), c(idx2): total 4. The c at index 2 was found only by following the output link from bc. ✓
Example 6 — Overlapping occurrences of the same pattern (C8)
Forecast: four as — is the answer 2 (non-overlapping) or 3 (overlapping)?

Steps.
- Trie: root →
a→aa(✓aa). Failure links: , . Why this step? Longest proper suffix ofaaisa, which is a node — this rope keeps oneaof progress after each match. - Scan
a a a a:- idx0
a: nodea, no report. - idx1
a:a→aa→ report aa (ends idx1). - idx2
a: :aahas no childa; ;a→aa→ report aa (ends idx2). - idx3
a: same fall-back → aa (ends idx3). Why this step? The failure link means after matching we still "remember" one trailinga, so the nextacompletes an overlapping copy. Aho-Corasick reports overlapping matches automatically.
- idx0
Verify: occurrences of aa in aaaa (overlapping) = at ending indices {1, 2, 3} → 3 matches. ✓
Example 7 — Real-world word problem (C9)
Forecast: does badman count as its own hit and still let bad/mad inside it count?
Steps.
- Trie:
b→ba→bad(✓bad)→badm→badma→badman(✓badman), andm→ma→mad(✓mad). - Failure links that matter:
- (suffix
mis a node). - longest proper suffix of
badmanthat is a node;manis not a pattern-prefix,an,nare not nodes → . Why this step? We must know wherebadm...falls back somadinsidebadmcan be caught.
- (suffix
- Scan
b a d m a d b a d m a n(indices 0..11):- idx2
d: nodebad→ bad. - idx3
m: nodebadm(real edge,bad→mexists). - idx4
a:badm→badma. - idx5
d:badma→ no childd? Pathbadmahas childnonly. : (suffixmais a node),mahas childd→ nodemad→ mad. Why this step? The rope from thebadm...branch lands us in themadbranch — catching an embedded word. - idx6
b…idx8d: rebuildbad→ bad (second one). - idx9
m→idx11n: continuebadm badma badman→ at idx11 nodebadman→ badman.
- idx2
- Total reports.
bad(idx0–2),mad(idx3–5),bad(idx6–8),badman(idx6–11). Why this step?badmanreports as itself; thebadprefix inside it already reported at idx8 — nested matches all count.
Verify: hits = 2×bad + 1×mad + 1×badman = 4 total flagged occurrences. ✓
Example 8 — Exam twist: fail is a node, not a child (C10)
Forecast: does the failure link ever point below its target string's length?
Steps.
- Definition recall. is the node whose string equals the longest proper suffix of . It is never a child of that node — a child would be longer, contradicting "suffix." Why this step? This is the exact trap in the parent's mistake list ("failure links go to a child of the suffix node"). We test it.
- Compute.
ab. Its proper suffixes areband `` (empty). The longest that is a trie node isb. So nodebitself. Why this step? Matching the definition directly, not the intuition-trap. - Verdict. The claim is FALSE. node
b, which has depth 1 (shorter thanab's depth 2). A failure link always points upward or sideways to strictly shorter depth, never to a deeper child.
Verify: depth of = 1 < depth of ab = 2, and b is a proper suffix of ab. Both conditions of the definition hold; the "child" claim violates the depth inequality. ✓
Recall Which cells did we cover?
C1 empty text ::: Ex 1 C2 single-char patterns ::: Ex 1 C3 miss at root ::: Ex 2 C4 nested patterns ::: Ex 3 C5 suffix-sharing dictionary chain ::: Ex 3 C6 deep failure jump ::: Ex 4 C7 multiple matches from one state ::: Ex 5 C8 overlapping same-pattern ::: Ex 6 C9 real-world filter ::: Ex 7 C10 fail-vs-goto exam trap ::: Ex 8
See also
- KMP — single pattern matching — the failure function this generalises.
- Trie — prefix tree — the skeleton we drove through.
- BFS — breadth-first search — the order that made failure links computable.
- Finite Automata — DFA/NFA — what the completed table is.
- Z-algorithm and string matching, Suffix Automaton, Suffix Tree — cousins for other string tasks.