A trie follows one edge per symbol in a key. Prefix lookup and insertion take O(L) time for key length L, independent of the number of stored keys under constant-time child lookup assumptions. A path existing does not mean its prefix is a stored complete key, so a terminal flag is necessary. Tries can use substantial memory because each node has child mappings and references. Normalize case and Unicode according to the application's identifier contract before insertion; otherwise visually similar labels may take different paths and produce confusing search results.
Tries: make prefix search distinct from complete-key lookup
Operational case
A depot indexes part codes AX47 and AX52. Prefix AX exists, but it is not a complete part code. The terminal marker on AX47 distinguishes an exact match from a prefix suggestion. The two codes share the AX nodes, which makes prefix filtering natural. A general substring search inside arbitrary product descriptions is a different problem; this trie only follows prefixes from the root. For a small catalog, a sorted list may be simpler and use less memory.
Working Python program
part_root = {}
for part_code in ("AX47", "AX52"):
current = part_root
for symbol in part_code:
current = current.setdefault(symbol, {})
current["$end"] = True
def lookup(part_code):
current = part_root
for symbol in part_code:
if symbol not in current:
return False
current = current[symbol]
return "$end" in current
print(lookup("AX"), lookup("AX47"))Output
False TrueTime, space, and tradeoff
The two insertions take O(total characters) time and space. Lookup is O(L) expected time with hash-backed child maps. The sentinel key is safe here because symbols are individual characters and no input character equals the multi-character marker; a typed node with a separate boolean is clearer in a larger implementation. Deletion needs to unmark the terminal and prune only nodes no longer shared by another code. Prefix listing also pays for every descendant key returned, beyond the O(L) path walk.
Common Mistakes
- Do not report every existing prefix as a full stored key.
- Do not mistake prefix search for arbitrary substring search.
- Do not prune a shared branch while deleting one key.
Connected lessons
- Trees and Heaps
- DSA Tutorial
- Binary search trees: preserve order through every branch
- Hash sets: fast membership without an order promise
- Graphs: adjacency lists and breadth-first reachability
- Binary heaps: select the next priority with a tie rule
- Project: choose structures for a dispatch board
- Project: audit depot connectivity and daily ranges
- Tree, graph, and range structure decisions
Failure-linked tries: find overlapping alert terms in one scan handles a related query contract.
Binary tries: choose a maximum-XOR fingerprint adds a related operation contract.
Front-coded lexicons: store shared prefixes within sorted term blocks adds a related indexing contract.
Level-order unary degree tries: encode child runs as bits adds a compact lookup contract.
Ternary search trees: branch by character and continue prefixes adds a keyed lookup comparison.
LZ78 phrase tries: emit dictionary index and next symbol adds a related structure with a different operation boundary.
Double-array tries: static incident-code transitions with BASE and CHECK examines a related structure with a different operation boundary.
Minimal acyclic dictionaries: merge equivalent word suffix states examines a related structure with a different operation boundary.
