Trie Data Structure for String Problems: A Practical Interview Guide
Learn how tries solve prefix-heavy string problems like autocomplete and word search, with Python examples, complexity analysis, and interview tips.
Why tries show up so often in interviews
If a problem asks you to match words by prefix, search a dictionary efficiently, or explore many string branches at once, a trie is often the cleanest tool for the job. Interviewers like tries because they test whether you can turn repeated string comparisons into structured traversal.
A trie, also called a prefix tree, stores strings character by character. That sounds simple, but it unlocks powerful patterns:
- Autocomplete: find all words that start with a prefix.
- Word search: scan a board while walking a dictionary at the same time.
- Prefix queries: test whether any word starts with a given string.
- Dictionary operations: insert, search, and delete words efficiently.
If you know the core idea, trie problems become much less mysterious. The trick is to stop thinking about whole words and start thinking about shared prefixes.
Problem statement: when a hash set is not enough
Suppose you have a list of words and you need to answer questions like:
- Does this exact word exist?
- Is there any word that starts with this prefix?
- What words can I reach from this partial string?
A hash set handles exact lookup well, but it does not naturally support prefix traversal. You could scan every word for every query, but that becomes expensive fast.
A trie organizes words so shared prefixes are stored once. For example, with:
catcarcaredog
The words cat, car, and care share the path c -> a -> .... That shared structure is what makes the trie useful.
Intuition: store prefixes as paths
Each node in a trie represents a prefix. Each edge corresponds to one character. A node usually contains:
- a map from character to next node
- a boolean flag like
is_wordto mark complete words
Think of it like walking down a tree:
- root = empty prefix
- first level = one-letter prefixes
- second level = two-letter prefixes
- and so on
This gives you two major benefits:
- Prefix queries are fast because you follow characters directly.
- Multiple words with shared prefixes reuse nodes, saving repeated work.
The tradeoff is memory. Tries are often faster for prefix-heavy tasks, but they use more space than a simple list or set.
Core trie implementation in Python
Let’s build a basic trie with three operations:
insert(word)search(word)starts_with(prefix)
class TrieNode:
def __init__(self):
self.children = {}
self.is_word = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word: str) -> None:
node = self.root
for ch in word:
if ch not in node.children:
node.children[ch] = TrieNode()
node = node.children[ch]
node.is_word = True
def search(self, word: str) -> bool:
node = self.root
for ch in word:
if ch not in node.children:
return False
node = node.children[ch]
return node.is_word
def starts_with(self, prefix: str) -> bool:
node = self.root
for ch in prefix:
if ch not in node.children:
return False
node = node.children[ch]
return TrueWorked example 1: basic insert and search
trie = Trie()
trie.insert("cat")
trie.insert("car")
trie.insert("care")
print(trie.search("car")) # True
print(trie.search("ca")) # False
print(trie.starts_with("ca")) # True
print(trie.starts_with("cap")) # FalseWhat happens internally
When you insert cat, the trie creates nodes for c, a, t, then marks t as a word end.
When you insert car, it reuses c -> a, then creates r.
When you insert care, it reuses c -> a -> r, then creates e.
That reuse is the whole point. You are compressing repeated prefixes into a shared structure.
Step-by-step approach for interview problems
Most trie interview problems follow the same pattern:
1. Build the trie from the input dictionary
If the problem gives you a word list, insert each word into the trie first.
2. Traverse the trie while processing the query
For prefix lookup, walk the trie character by character.
For backtracking problems like word search, combine trie traversal with DFS on the board.
3. Use is_word to detect completed matches
A prefix is not always a full word. Marking word ends avoids false positives.
4. Prune dead branches when possible
If a subtree can no longer lead to a solution, stop exploring it. This is especially useful in backtracking problems.
Worked example 2: autocomplete suggestions
A common interview task is to return all words matching a prefix.
Here is a helper that finds the node for a prefix, then collects all words below it.
class TrieNode:
def __init__(self):
self.children = {}
self.is_word = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word: str) -> None:
node = self.root
for ch in word:
if ch not in node.children:
node.children[ch] = TrieNode()
node = node.children[ch]
node.is_word = True
def _find_node(self, prefix: str):
node = self.root
for ch in prefix:
if ch not in node.children:
return None
node = node.children[ch]
return node
def autocomplete(self, prefix: str):
node = self._find_node(prefix)
if not node:
return []
results = []
def dfs(curr, path):
if curr.is_word:
results.append(prefix + ''.join(path))
for ch, nxt in curr.children.items():
path.append(ch)
dfs(nxt, path)
path.pop()
dfs(node, [])
return resultsExample usage
trie = Trie()
for word in ["cat", "car", "care", "cart", "dog"]:
trie.insert(word)
print(trie.autocomplete("car"))
# ['car', 'care', 'cart']Why this works
You first walk to the node that represents car. From there, every word in that subtree starts with car.
This is a common interview move: use the trie to narrow the search space, then use DFS to enumerate matches.
Worked example 3: word search with a trie
One of the most important trie applications is word search on a board. Given a 2D grid of letters and a list of words, find all words that can be formed by moving horizontally or vertically.
A brute-force approach checks every word against every path, which is too slow. A trie lets you search all words at once.
Approach
- Insert all target words into a trie.
- Start DFS from each board cell.
- As you move, follow the trie branch for the current character.
- If a path is not in the trie, stop immediately.
- If you reach a node marked
is_word, record the word.
Python code
class TrieNode:
def __init__(self):
self.children = {}
self.is_word = False
self.word = None # store full word at terminal node
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word: str) -> None:
node = self.root
for ch in word:
if ch not in node.children:
node.children[ch] = TrieNode()
node = node.children[ch]
node.is_word = True
node.word = word
def find_words(board, words):
trie = Trie()
for word in words:
trie.insert(word)
rows, cols = len(board), len(board[0])
result = []
def dfs(r, c, node):
ch = board[r][c]
if ch not in node.children:
return
nxt = node.children[ch]
if nxt.is_word:
result.append(nxt.word)
nxt.is_word = False # avoid duplicates
board[r][c] = '#' # mark visited
for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and board[nr][nc] != '#':
dfs(nr, nc, nxt)
board[r][c] = ch
for r in range(rows):
for c in range(cols):
dfs(r, c, trie.root)
return resultExample usage
board = [
['o', 'a', 'a', 'n'],
['e', 't', 'a', 'e'],
['i', 'h', 'k', 'r'],
['i', 'f', 'l', 'v']
]
words = ["oath", "pea", "eat", "rain"]
print(find_words(board, words))
# ['oath', 'eat']Why the trie helps here
Without a trie, you would repeatedly attempt to match each word from each cell. With a trie, you only explore paths that match some dictionary prefix.
That pruning is a major performance improvement.
Complexity analysis
Trie complexity depends on the total length of inserted words, not just the number of words.
Basic trie operations
| Operation | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Insert word | O(L) | O(L) | L is word length, new nodes may be created |
| Search word | O(L) | O(1) | Walk the characters once |
| Prefix check | O(P) | O(1) | P is prefix length |
| Autocomplete | O(P + M) | O(M) | M depends on number of returned characters/words |
Word search comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Check each word separately with DFS | Very large, often O(W * R * C * 4^K) | O(K) | W = number of words, K = word length |
| Trie + DFS on board | Pruned significantly in practice | O(total trie size + recursion) | Usually the preferred interview solution |
The key idea is that the trie reduces duplicate work across words with shared prefixes.
Common pitfalls
1. Confusing prefix with full word
starts_with("car") may be true even if car itself is not a valid word unless is_word is set.
2. Forgetting to mark visited cells in board search
In word search, you must avoid revisiting the same cell in one path.
3. Not pruning duplicates
If the same word can be found multiple ways, you may need to deduplicate results.
4. Overcomplicating the node structure
For many problems, a dictionary of children and one terminal flag are enough. Keep the node simple unless you need extra metadata.
When to reach for a trie
A trie is a strong fit when the problem has one or more of these traits:
- lots of shared prefixes
- repeated prefix lookups
- dictionary-style search
- autocomplete or suggestion ranking
- grid/backtracking combined with a word list
A trie is usually not the first choice if you only need exact membership checks. A set is simpler and often faster for that case.
Related patterns and variations
Once you understand the trie, you will notice several common interview variations:
- Map-based trie nodes for general alphabets
- Array-based trie nodes for lowercase English letters
- Compressed trie / radix tree for space savings
- Prefix counting for problems like “how many words start with this prefix?”
- Longest common prefix using a trie or incremental scanning
Key takeaways
- A trie stores strings as shared prefix paths.
- It makes prefix search and autocomplete efficient.
- It is especially useful when multiple words share structure.
- In backtracking problems like word search, a trie lets you prune invalid paths early.
- Keep the implementation simple: children map plus terminal flag is enough for many problems.
Practice these next
Try these problems after you are comfortable with the basic trie pattern:
- Implement Trie (Prefix Tree)
- Add and Search Word
- Word Search II
- Replace Words
- Longest Common Prefix
If you want to see the trie traversal and pruning happen step by step, I built an interactive version of this walkthrough at codebrew.ai — might help if you are a visual learner.

