A trie (pronounced "try" or "tree" depending on which side of the religious war you're on) is a tree where each node represents a character / byte / element of a key, and the path from root to node spells the key. It's specifically good at problems where you want to do prefix queries — autocomplete, longest-prefix match, dictionary scans. Where it shines, nothing else comes close.
Insert "cat", "car", "dog":
(root)
/ \
c d
| |
a o
/ \ |
t r g
Each node holds (a) a marker indicating whether a key ends here, (b) child pointers / map per next character. Leaves can hold associated values (key → value mapping).
Two costs:
n keys of average length L, the trie has up to n * L nodes. Many implementations use child maps (HashMap, sparse array) to keep memory bounded.O(L) where L is the key length. Independent of n (the number of keys). Worth contrasting with hash table's O(L) for hashing the key plus O(1) for the table lookup — for very long keys, hash table loses; for short keys, hash table wins.Given prefix "ap", find all keys starting with "ap." Trivial in a trie: descend to the "ap" node; all keys in its subtree are matches. O(prefix length + matches).
In a hash table: impossible without scanning everything. In a sorted array: O(log n + matches) via binary search on prefix bounds. Trie wins on the worst case.
Production examples: search suggestions, command-line completion, IDE code completion (the simple kind, not the LLM kind).
Given an IP address, find the most specific prefix in a routing table that matches. Routers do this on every packet.
A trie (specifically a "patricia trie" or "radix trie") solves this in O(prefix length). Hash tables can't do "longest match"; they can only do exact match.
Production examples: IP routing tables (Linux's FIB), CIDR-based access control, geographic routing.
For a misspelled word, find candidate corrections by exploring the trie within edit distance ≤ 2. The trie structure makes this efficient because branches that diverge from your word can be pruned early.
Production examples: spell checkers (aspell, hunspell), search query corrections.
O(query length) regardless of text length. Used in genome alignment, log analysis.Compresses chains of single-child nodes into one. Saves memory; insert / lookup is faster.
Linux uses a variant of this for the routing table (fib_trie).
Each node has three children (less, equal, greater). Cache-friendlier than a wide trie when the alphabet is large. Used in Apache Commons.
Cache-conscious; combines tries with hash tables at the leaves. Faster than plain tries on real-world string sets.
Tries with shared subtree structure. Enormously space-efficient for natural-language dictionaries (English wordlist fits in ~100KB).
The naive C++/Java trie uses Map<Character, Node> per node. For ASCII text, prefer Node[256] arrays — direct indexing, no hash overhead. For Unicode strings, the array would be huge; use a sparse representation.
Cache-aware layouts pack frequently-accessed nodes together. Patricia tries / radix tries help; HAT-tries help further; DAFSAs maximally so.
In hot paths (e.g., per-packet routing), a radix-trie implementation laid out cache-friendly is dramatically faster than a generic hashmap-of-hashmap trie.
class Trie:
def __init__(self):
self.children = {}
self.value = None
self.is_end = False
def insert(self, key, value=None):
node = self
for c in key:
if c not in node.children:
node.children[c] = Trie()
node = node.children[c]
node.is_end = True
node.value = value
def search(self, key):
node = self._descend(key)
return node.value if node and node.is_end else None
def starts_with(self, prefix):
node = self._descend(prefix)
if node is None: return []
results = []
self._collect(node, prefix, results)
return results
def _descend(self, key):
node = self
for c in key:
if c not in node.children: return None
node = node.children[c]
return node
def _collect(self, node, prefix, results):
if node.is_end:
results.append((prefix, node.value))
for c, child in node.children.items():
self._collect(child, prefix + c, results)
Adequate for small data, autocomplete prototypes. Not optimised for memory or cache; use a library for production scale.
pygtrie (Python) — pure Python; flexible.marisa-trie (Python wrapper around C++ MARISA) — read-only after construction; very compact.datrie (Python wrapper around libdatrie) — double-array trie; fast.hat-trie (C++) — cache-conscious; high-performance.patricia_trie (Rust crate) — radix trie.TreeMap with prefix range queries — not exactly a trie, but covers the prefix-query case for sorted strings.For production: pick a library that implements the variant matching your access pattern. Don't write your own unless you have a specific reason.
fib_trie — IPv4/IPv6 routing decisions on every packet.If you're working on networking, search, bioinformatics, or text-processing, you'll meet tries in production.