Skip to main content
TrieData StructuresPatternsString

Trie Data Structure — Complete LeetCode Interview Guide [2026]

· 10 min read

Syed Peera Saheb

Software Engineer · 5+ years in tech interviews

Summary

Tries solve prefix-matching and autocomplete in O(L) time, beating hash maps for prefix queries. Full implementation plus the 8 interview Trie problems.

Trie structure and node implementation

Two implementation choices: (1) children as a dict (flexible, handles any character set) vs children as a list of 26 (faster access, fixed to lowercase letters). For interviews, use a dict — it is more readable and handles edge cases. The is_end flag is what distinguishes a complete word from a mere prefix. "app" and "apple" both exist in the trie, but only "apple" would have is_end=True at the "e" node if only "apple" was inserted.

Code template

class TrieNode:
    def __init__(self):
        self.children = {}   # char -> TrieNode
        self.is_end = False

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

Insert, search, and startsWith templates

These three methods are identical except for the return condition at the end. Practice writing all three from memory — LC 208 asks for exactly this.

Code template

def insert(self, word):
    node = self.root
    for char in word:
        if char not in node.children:
            node.children[char] = TrieNode()
        node = node.children[char]
    node.is_end = True

def search(self, word):
    node = self.root
    for char in word:
        if char not in node.children:
            return False
        node = node.children[char]
    return node.is_end

def startsWith(self, prefix):
    node = self.root
    for char in prefix:
        if char not in node.children:
            return False
        node = node.children[char]
    return True

Word Search II — combining Trie with DFS (LC 212)

Word Search II is the most complex Trie problem in interviews. Build a Trie from all words in the dictionary. Then run DFS on the grid from each cell, traversing the Trie simultaneously with the grid traversal. When you reach an is_end=True node, record the word. The key optimization: instead of searching the grid for each word independently (O(W * 4^L)), you search the grid once and prune branches where no Trie path exists (O(R * C * 4^L) total, much better in practice). Mark is_end=False after finding a word to avoid duplicates. This pattern — Trie as a search guide for another traversal — appears in several advanced problems.

Wildcard search: Add and Search Words (LC 211)

When a search pattern contains "." (match any character), you cannot follow a single Trie path — you must branch to all children at that position. The recursion handles dots by trying all children. This DFS approach generalizes to regex-like matching in Tries.

Code template

def search(self, word):
    def dfs(node, i):
        if i == len(word):
            return node.is_end
        if word[i] == ".":
            return any(dfs(child, i + 1) for child in node.children.values())
        if word[i] not in node.children:
            return False
        return dfs(node.children[word[i]], i + 1)
    return dfs(self.root, 0)

Replace Words (LC 648) — Trie for shortest prefix

Given a dictionary of root words and a sentence, replace each word with its shortest root prefix from the dictionary. Naive approach: for each word, check all dictionary roots — O(W * D * L). Trie approach: insert all roots into a Trie, then for each word, traverse the Trie until you find an is_end node (the shortest root) or exhaust the word. O(W * L) total. This pattern — using a Trie to find the shortest matching prefix — appears in multiple interview problems.

Code template

def replace_words(self, dictionary, sentence):
    trie = Trie()
    for root in dictionary:
        trie.insert(root)
    result = []
    for word in sentence.split():
        node = trie.root
        for i, char in enumerate(word):
            if char not in node.children:
                break
            node = node.children[char]
            if node.is_end:
                word = word[:i + 1]
                break
        result.append(word)
    return " ".join(result)

Trie vs alternatives: when to choose which

Trie vs hash set: both O(L) for exact lookup, but Trie additionally supports O(L + K) prefix enumeration (where K is the count of matching words). Choose hash set for exact lookups, Trie when prefix queries matter. Trie vs sorted list + binary search: sorted list supports prefix queries via binary search (O(log N + K)), but insertion is O(N). Trie insertion is O(L). Choose Trie for dynamic dictionaries with frequent insertions and prefix queries. Trie vs suffix array: suffix arrays are more space-efficient for static text; Tries are better for dynamic word sets. In interviews, Tries are always the right answer for prefix/autocomplete problems — suffix arrays are almost never required.

Frequently Asked Questions

What is a Trie data structure?
A Trie (also called prefix tree or digital tree) is a tree-shaped data structure where each node represents a single character, and paths from the root to a node represent prefixes of stored strings. Each node has up to 26 children (for lowercase English letters) and a boolean flag marking whether that node is the end of a complete word. The key advantage: searching for a word or prefix takes O(L) time where L is the length of the search string, regardless of how many words are stored. Hash maps also offer O(L) lookup, but Tries additionally support O(L) prefix enumeration, which hash maps cannot do efficiently.
When should I use a Trie instead of a hash map?
Use a Trie when the problem involves prefix matching, autocomplete, or finding all words with a given prefix. Hash maps are O(1) for exact lookups but O(N*L) for prefix queries (you must check all N stored words). Tries are O(L) for both exact and prefix queries. The decision rule: if the problem uses the word "prefix", "starts with", "autocomplete", or "search for words matching pattern", reach for a Trie. If the problem is just "does this exact word exist?", a hash set is simpler and equally efficient.
What is the time and space complexity of a Trie?
Time complexity: Insert O(L), Search O(L), StartsWith O(L) — where L is the length of the word or prefix. Space complexity: O(N * L * A) where N is the number of words, L is the average word length, and A is the alphabet size (26 for lowercase English). In the worst case (no shared prefixes), this is worse than a hash set. In practice, shared prefixes (common in English) make Tries space-efficient. For interview problems with a small word set (< 10,000 words), space is rarely a concern.
What are the most common Trie LeetCode problems?
Must-know Trie problems: Implement Trie (LC 208) — the foundational problem; implement insert, search, and startsWith. Word Search II (LC 212) — Trie + DFS on grid; a Hard problem that combines two patterns. Design Add and Search Words Data Structure (LC 211) — Trie with wildcard DFS for the dot character. Replace Words (LC 648) — find shortest root prefix in a dictionary. Map Sum Pairs (LC 677) — Trie with integer values. Longest Word in Dictionary (LC 720). Word Break II (LC 140) — Trie accelerates the DP lookups. Autocomplete System (LC 642) — Trie + frequency ranking.

Practice this pattern

See all problems and the code template →

Study pattern

Syed Peera Saheb

Software Engineer · 5+ years · ServiceNow

Software engineer with hands-on experience passing technical interviews at top tech companies. Built Coding Prep Guide to share the pattern-first prep strategy that actually works. Writes about DSA, system design, and interview strategy.

Buy me a coffee