Skip to main content
Samarth Sugandhi
EditReport

Trie (Prefix Tree)

A Trie (pronounced "try", from retrieval) is a tree-based data structure designed for storing and searching strings with shared prefixes. Unlike a HashMap, a Trie lets you search by prefix in O(m) time where m is the length of the word โ€” making it the backbone of search autocomplete, spell checkers, and IP routing.


๐Ÿง  Why Trie? โ€” Comparison with Other Structuresโ€‹

Store words: ["apple", "app", "apt", "bat", "ball"]
Then answer: "Give all words starting with 'ap'"

โŒ Array/List:
Scan every word โ†’ O(n ร— m) per query โ†’ slow for large dictionaries

โŒ HashMap:
O(1) exact lookup but NO prefix search support
"app" โ†’ exists, but "give all words starting with ap" requires scanning all keys

โœ… Trie:
Insert: O(m) per word
Prefix search: O(m) to reach prefix node, then collect all words below โ†’ Fast!

๐ŸŒณ Trie Structure โ€” Visual Diagramโ€‹

Words inserted: ["app", "apple", "apt", "bat", "ball"]

root
/ \
a b
| |
p a
/ \ / \
p t t l
| | | |
[โœ“] [โœ“][โœ“] l
| |
l [โœ“]
|
e
|
[โœ“]

[โœ“] = isEndOfWord is true

Paths from root:
rootโ†’aโ†’pโ†’p = "app" โœ“
rootโ†’aโ†’pโ†’pโ†’lโ†’e = "apple" โœ“
rootโ†’aโ†’pโ†’t = "apt" โœ“
rootโ†’bโ†’aโ†’t = "bat" โœ“
rootโ†’bโ†’aโ†’lโ†’l = "ball" โœ“

๐Ÿ”ฉ Node Structureโ€‹

Each Trie node contains:
1. children[26] โ†’ array of 26 pointers (one per lowercase letter a-z)
children[0] = pointer to 'a' child
children[1] = pointer to 'b' child
...
children[25] = pointer to 'z' child
2. isEndOfWord โ†’ boolean flag: true if a valid word ends at this node

Initially all children = null, isEndOfWord = false

โž• Operation 1: Insertโ€‹

Visual Dry-Run โ€” Insert "app"โ€‹

Start at root. Word = "app"

Character 'a':
root.children[0] == null? โ†’ YES โ†’ create new node
Move to children[0]

Character 'p':
node.children[15] == null? โ†’ YES โ†’ create new node
Move to children[15]

Character 'p':
node.children[15] == null? โ†’ YES โ†’ create new node
Move to children[15]

End of word โ†’ set isEndOfWord = true โœ“

root โ†’ [a] โ†’ [p] โ†’ [p*] (* = end of word)

Visual Dry-Run โ€” Insert "apple" after "app"โ€‹

Word = "apple"

'a' โ†’ node exists (root.children[0]) โ†’ just move, no new node
'p' โ†’ node exists โ†’ just move
'p' โ†’ node exists โ†’ just move (this was end of "app", stays end)
'l' โ†’ null โ†’ create new node, move
'e' โ†’ null โ†’ create new node, move
End โ†’ isEndOfWord = true โœ“

root โ†’ [a] โ†’ [p] โ†’ [p*] โ†’ [l] โ†’ [e*]

Codeโ€‹

class TrieNode:
def __init__(self):
self.children = {} # char โ†’ TrieNode
self.is_end_of_word = False

class Trie:
def __init__(self):
self.root = TrieNode()

def insert(self, word: str) -> None:
node = self.root
for char in word:
if char not in node.children:
node.children[char] = TrieNode() # create node if missing
node = node.children[char] # move to next node
node.is_end_of_word = True # mark end of word

# Example
trie = Trie()
trie.insert("app")
trie.insert("apple")
trie.insert("apt")

Time Complexity: O(m) where m = length of word Space Complexity: O(m) per word inserted


๐Ÿ” Operation 2: Search (Exact Word)โ€‹

Visual Dry-Runโ€‹

Trie contains: ["app", "apple", "apt"]

root โ†’ [a] โ†’ [p] โ†’ [p*] โ†’ [l] โ†’ [e*]
โ†˜ [t*]

Search "app":
'a' โ†’ exists โ†’ move
'p' โ†’ exists โ†’ move
'p' โ†’ exists โ†’ move
End โ†’ isEndOfWord = TRUE โœ… โ†’ "app" EXISTS

Search "ap":
'a' โ†’ exists โ†’ move
'p' โ†’ exists โ†’ move
End โ†’ isEndOfWord = FALSE โŒ โ†’ "ap" is NOT a word
(node exists as prefix but not marked as complete word)

Codeโ€‹

    def search(self, word: str) -> bool:
node = self.root
for char in word:
if char not in node.children:
return False # path doesn't exist
node = node.children[char]
return node.is_end_of_word # true only if complete word

Time Complexity: O(m) ย |ย  Space Complexity: O(1)


Visual Dry-Runโ€‹

startsWith("ap"):
'a' โ†’ exists โ†’ move
'p' โ†’ exists โ†’ move
Reached end of prefix โ†’ node exists โ†’ TRUE โœ…

startsWith("bx"):
'b' โ†’ exists โ†’ move
'x' โ†’ does NOT exist โ†’ FALSE โŒ

Key difference:
search("ap") โ†’ FALSE (not a complete word)
startsWith("ap") โ†’ TRUE (it IS a valid prefix)

Codeโ€‹

    def starts_with(self, prefix: str) -> bool:
node = self.root
for char in prefix:
if char not in node.children:
return False # prefix path doesn't exist
node = node.children[char]
return True # prefix exists

Time Complexity: O(m) ย |ย  Space Complexity: O(1)


๐Ÿ—‘๏ธ Operation 4: Deleteโ€‹

Visual Dry-Runโ€‹

Delete "app" from Trie containing ["app", "apple", "apt"]

Before:
root โ†’ [a] โ†’ [p] โ†’ [p*] โ†’ [l] โ†’ [e*]
โ†˜ [t*]

Strategy: Only unmark isEndOfWord at the last node.
Do NOT delete the node โ€” "apple" still needs this path!

After deleting "app":
root โ†’ [a] โ†’ [p] โ†’ [p] โ†’ [l] โ†’ [e*] โ† 'p' no longer end of word
โ†˜ [t*]

search("app") โ†’ FALSE โœ… (deleted)
search("apple") โ†’ TRUE โœ… (intact)

Codeโ€‹

    def delete(self, word: str) -> bool:
if not self.search(word):
return False

def _delete(node, word, depth):
if depth == len(word):
node.is_end_of_word = False
return len(node.children) == 0

char = word[depth]
if _delete(node.children[char], word, depth + 1):
del node.children[char]
return len(node.children) == 0 and not node.is_end_of_word
return False

_delete(self.root, word, 0)
return True

Time Complexity: O(m) ย |ย  Space Complexity: O(m) recursion stack


โœจ Bonus: AutoComplete Featureโ€‹

Visual Dry-Runโ€‹

Trie: ["app", "apple", "apt", "bat", "ball"]
AutoComplete("ap"):

Step 1: Navigate to node for prefix "ap"
root โ†’ 'a' โ†’ 'p' โ†’ [at this node]

Step 2: DFS from this node collecting all complete words
โ†’ 'p'* โ†’ found "app"
โ†’ 'l' โ†’ 'e'* โ†’ found "apple"
โ†’ 't'* โ†’ found "apt"

Result: ["app", "apple", "apt"] โœ…

Codeโ€‹

class TrieWithAutoComplete(Trie):
def autocomplete(self, prefix: str) -> list:
node = self.root

# Navigate to prefix node
for char in prefix:
if char not in node.children:
return []
node = node.children[char]

# DFS to collect all words
results = []
self._dfs(node, prefix, results)
return results

def _dfs(self, node, current_word, results):
if node.is_end_of_word:
results.append(current_word)
for char, child_node in node.children.items():
self._dfs(child_node, current_word + char, results)

# Example
trie = TrieWithAutoComplete()
for word in ["app", "apple", "apt", "bat", "ball"]:
trie.insert(word)

print(trie.autocomplete("ap")) # ['app', 'apple', 'apt']
print(trie.autocomplete("ba")) # ['bat', 'ball']
print(trie.autocomplete("xyz")) # []

โšก Brute Force vs Trieโ€‹

Dictionary: 100,000 words, average length 8
Query: "Find all words starting with 'pre'"

โŒ BRUTE FORCE โ€” O(n ร— m):
Check every word: 100,000 ร— 8 = 800,000 operations per query

โœ… TRIE โ€” O(m + k):
Navigate prefix "pre": 3 steps
Collect k=500 matching words: 503 operations total

Speedup: ~1,600x faster โœ…

๐Ÿ“Š Complexity Summaryโ€‹

OperationTimeSpace
InsertO(m)O(m)
SearchO(m)O(1)
StartsWithO(m)O(1)
DeleteO(m)O(m) stack
AutoCompleteO(m + k)O(k)

m = length of word/prefix ย |ย  k = number of results returned


โŒ Common Mistakesโ€‹

  1. Confusing search() and startsWith() โ€” search("app") checks for a complete word. startsWith("app") checks for any word with that prefix. They return different results!
  2. Deleting shared prefix nodes โ€” Never delete a node if other words share its path. Always check children before removing.
  3. Case sensitivity โ€” Tries are case-sensitive. Always normalize input to lowercase before inserting or searching.
  4. Array vs HashMap for children โ€” Array children[26] is O(1) access but wastes memory. HashMap uses less memory but has overhead. Choose based on your character set size.
  5. Forgetting isEndOfWord โ€” Inserting "apple" without marking isEndOfWord=true causes search("apple") to return false even though the path exists.

๐Ÿ‹๏ธ Practice Problemsโ€‹

#ProblemConceptDifficulty
1Implement Trie (Prefix Tree)Insert, Search, StartsWith๐ŸŸก Medium
2Longest Common PrefixTrie traversal๐ŸŸข Easy
3Design Add and Search WordsWildcard search in Trie๐ŸŸก Medium
4Replace WordsPrefix replacement๐ŸŸก Medium
5Map Sum PairsTrie with values๐ŸŸก Medium
6Word Search IITrie + DFS on grid๐Ÿ”ด Hard
7Maximum XOR of Two NumbersBinary Trie๐Ÿ”ด Hard
8Stream of CharactersTrie + sliding window๐Ÿ”ด Hard

๐ŸŒ Real-World Applicationsโ€‹

  • Search autocomplete โ€” Google and Bing suggest completions as you type
  • Spell checker โ€” Word processors verify if a word exists in the dictionary
  • IP routing (LPM) โ€” Routers use binary Tries for Longest Prefix Match
  • T9 predictive text โ€” Mobile keyboard word prediction
  • DNA sequence matching โ€” Biological sequence databases

๐Ÿ”— Referencesโ€‹

Finished reading? Mark this topic as complete.