Skip to content

Trie

First PublishedByAtif Alam

Difficulty: Medium. ROI: Low.

Other common names: Prefix tree, digital tree, radix tree (when compressed).

When to use: Use when you need to store strings and support prefix lookups, “all keys with prefix X,” or word search in a grid. Good for autocomplete and dictionary-style problems.

Idea: Each node has children keyed by the next character. A path from the root spells a prefix; mark nodes where a word ends. Insert and search are O(m) for a string of length m.

Time / space: Insert and search O(m) per string. Space O(total characters in all strings) in the worst case.

Store strings in a tree where each edge is a character; a path from the root spells a prefix. Mark nodes where a word ends so we can distinguish full words from prefixes.

Sample input and output

t = Trie()
t.insert("cat")
t.insert("cats")
t.insert("car")
print(t.search("cat")) # True
print(t.search("ca")) # False (prefix only)
print(t.search("dog")) # False
  • Sample input: Insert “cat”, “cats”, “car”; then search “cat”, “ca”, “dog”.
  • Sample output: True, False, False — “cat” is a stored word; “ca” is only a prefix; “dog” is not in the trie.

Code (with inline comments)

class TrieNode:
def __init__(self):
self.children = {} # map next character -> child TrieNode
self.is_end = False # True if a word ends at this node
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word):
node = self.root
for c in word:
if c not in node.children:
node.children[c] = TrieNode()
node = node.children[c] # step down into the child
node.is_end = True # mark end of this word
def search(self, word):
node = self.root
for c in word:
if c not in node.children:
return False
node = node.children[c]
return node.is_end # must end at a word, not just a prefix

Line-by-line:

  • self.children = {} — Each node has a dict mapping a single character to the next node. Following a path from the root spells a prefix; different branches correspond to different words/prefixes.
  • self.is_end = False — We set this to True only at nodes where a complete word ends (so “cat” and “cats” share the path c→a→t, but only the “t” node for “cat” has is_end=True if we inserted “cat”).
  • node = self.root — Start at the root for each insert or search.
  • if c not in node.children: node.children[c] = TrieNode() — If there’s no edge for this character, create a new node. Then step to that child with node = node.children[c].
  • node.is_end = True — After processing all characters, we’re at the node that represents this full word; mark it so we can distinguish “cat” from “cats” (prefix only).
  • return node.is_end — We only return True if we followed the whole word and ended at a node marked as an end-of-word (not just a prefix).

Execution trace (numbered list)

  1. Step 1: insert(“cat”). root → c → a → t, mark t.is_end=True.
  2. Step 2: insert(“cats”). Follow c→a→t, add child ‘s’, mark s.is_end=True.
  3. Step 3: insert(“car”). Follow c→a, add child ‘r’, mark r.is_end=True.
  4. Step 4: search(“cat”). Follow c→a→t, return t.is_end → True.
  5. Step 5: search(“ca”). Follow c→a, return a.is_end → False.

Execution trace (code with step comments)

# insert("cat"): root -c-> -a-> -t-> (is_end)
# insert("cats"): follow to t, add -s-> (is_end)
# insert("car"): follow c->a, add -r-> (is_end)
# search("cat"): c->a->t, is_end=True → True
# search("ca"): c->a, is_end=False → False

Execution trace (ASCII diagram)

Trie after inserting “cat”, “cats”, “car”. * marks end-of-word.

root
|
c
|
a
/ \
t* r*
|
s*