Solution (source code)

= Solution

Allocate nodes only as required, explicitly initializing every field:
``
PROCEDURE Make(VAR t: Trie);
VAR c: INTEGER;
BEGIN
  NEW(t); t.terminal := FALSE; c := 0;
  WHILE c < 26 DO t.child[c] := NIL; c := c+1 END
END Make;

PROCEDURE AddTail(VAR t: Trie; s: ARRAY OF CHAR; i: INTEGER);
BEGIN
  IF t = NIL THEN Make(t) END;
  IF s[i] = 0X THEN
    t.terminal := TRUE
  ELSE
    AddTail(t.child[Index(s[i])], s, i+1)
  END
END AddTail;

PROCEDURE Add(VAR t: Trie; s: ARRAY OF CHAR);
BEGIN
  AddTail(t, s, 0)
END Add;
``
Here `0X` is the zero character, and `s` must contain its terminator within its <array> bounds. `Make` establishes that the first `c` child fields are `NIL` as its <loop invariant>. Passing `t` by reference lets the base allocation replace a missing root or child pointer.

For correctness, use <induction> on the number of characters before the terminator. At length zero, setting `terminal` adds exactly the empty suffix. Otherwise the chosen child stores exactly words with the required next letter, and the <induction hypothesis> adds the remaining suffix while preserving all other words. Thus \b[the resulting <set> is the old <set> union the supplied word, ignoring case]. Repeated insertion changes no word membership. A newly allocated path reaches the newly terminal node, preserving the canonical <representation invariant> after the procedure finishes. The <running time> is $O(1+\ell)$ for a word of length $\ell$, using $O(1+\ell)$ <call stack> space without optimization.