Implement Trie (Prefix Tree)

Each node holds up to 26 children and an end-of-word flag. Insert, search and startsWith all walk one child per letter.

Time O(length) per operationSpace O(total letters inserted)Code C#
Watch it animatedStep through it, with the running line highlighted. Free with an account.

The idea

A trie stores words letter by letter along paths from the root, so words with the same prefix share their first nodes. Inserting walks the path, creating any missing child, and marks the last node as the end of a word. Search and startsWith both walk the same path with Find; they differ only at the end: startsWith is satisfied by reaching the node at all, while search also needs that node to be marked as a word end.

Code

public class Trie {
    class Node { public Node[] Next = new Node[26]; public bool End; }
    readonly Node root = new();

    public void Insert(string word) {
        var cur = root;
        foreach (char c in word) {
            if (cur.Next[c - 'a'] == null) cur.Next[c - 'a'] = new Node();
            cur = cur.Next[c - 'a'];
        }
        cur.End = true; // a whole word ends here
    }

    public bool Search(string word) => Find(word)?.End == true;
    public bool StartsWith(string prefix) => Find(prefix) != null;

    Node Find(string s) {
        var cur = root;
        foreach (char c in s) {
            cur = cur.Next[c - 'a'];
            if (cur == null) return null; // nothing was inserted with this letter here
        }
        return cur;
    }
}

Tips

In the .NET library

Find(word)?.End == true uses the null-conditional operator: if Find returns null, the whole expression is null, and null == true is false.