Tries

Topics in this section
2 cards

Tries: a tree of prefixes

A trie (prefix tree) stores strings one character per level. The root is empty; each edge is a letter; a word is a path from the root, and a node is marked where a word ends. Words that share a prefix share the path: 'car', 'cart' and 'cat' all start with the same c → a.

Looking up a word costs O(length of the word), however many words are stored. More usefully, you can find every word with a given prefix: walk down the prefix, then collect everything below. A hash map can tell you whether 'cat' exists, but not which words start with 'ca' without checking every key.

A trie holding car, cart, cat and dog; word ends are marked
In real life

Search-box autocomplete, spell checkers, and IP routing tables, where routers find the longest matching address prefix using a trie over bits.

In code: Python · Rust
class Trie:
    def __init__(self):
        self.root = {}
    def add(self, word):
        node = self.root
        for ch in word:
            node = node.setdefault(ch, {})
        node["$"] = True                 # end-of-word mark
    def starts_with(self, prefix):
        node = self.root
        for ch in prefix:
            if ch not in node: return []
            node = node[ch]
        out, stack = [], [(node, prefix)]
        while stack:
            n, s = stack.pop()
            if "$" in n: out.append(s)
            stack += [(c, s + k) for k, c in n.items() if k != "$"]
        return sorted(out)
  • Each node here is a small map from character to child. For a fixed alphabet, an array of 26 slots is faster but uses more memory.
  • The end-of-word mark matters: 'car' is a word, 'ca' is only a prefix.
Check yourself

A trie holds 100,000 words. What does checking whether 'cart' is one of them cost?

When a trie is worth it

A trie wins when the question is about prefixes: autocomplete, 'all keys starting with', longest matching prefix. For plain 'is this exact word in the set?', a hash set is simpler and usually faster.

The cost is memory: every node is a separate object with its own child map, and long words with no shared prefixes create long chains of single-child nodes. Production systems often use a compressed trie (radix tree), which merges single-child chains into one edge labelled with a whole substring.

In real life

Linux uses radix trees in its memory management; many HTTP routers in Go and Rust use a radix tree to match URL paths like /users/:id/orders.

Check yourself

You only need to check whether a username is already taken. Which structure is the simpler good fit?