← Back to DevBytes

Interview Guide: Tries Problems and Solutions

Understanding Tries

A trie, pronounced "try" or "tree", is a tree-like data structure used to store an associative array where the keys are usually strings. Unlike a binary search tree, nodes in a trie do not store the entire key directly. Instead, each node represents a single character, and the path from the root to a node spells out a prefix of a key. A node that marks the end of a valid word is often flagged with a boolean or a special marker.

Core Concepts

The trie consists of:

The primary operations are insertion, search, and prefix lookup, all running in O(k) time where k is the length of the key. Space complexity is O(ALPHABET_SIZE * average_word_length * n) but can be optimized.

Why Tries Matter in Interviews

Tries appear frequently in coding interviews, especially at top-tier companies. They test your understanding of tree traversal, recursion, and space-time tradeoffs. Problems involving dictionaries, autocomplete, spell checkers, IP routing (longest prefix matching), and word games like Boggle are classic trie scenarios. Mastering tries demonstrates a strong grasp of advanced data structures.

Implementing a Trie from Scratch

πŸš€ Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

Let’s build a basic trie in Python. We'll define a TrieNode class and a Trie class with insertion, search, and prefix methods.

Trie Node Definition

class TrieNode:
    def __init__(self):
        self.children = {}  # dictionary mapping character -> TrieNode
        self.is_end = False  # True if the node marks the end of a word

Complete Trie Class

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()
            node = node.children[char]
        node.is_end = True

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

    def starts_with(self, prefix: str) -> bool:
        node = self.root
        for char in prefix:
            if char not in node.children:
                return False
            node = node.children[char]
        return True

This implementation uses a dictionary for children to support any character set. The starts_with method checks if any word exists that starts with the given prefix, but doesn't require that prefix to be a complete word.

Deletion Operation

Deletion is trickier. We must remove nodes that are no longer needed. A recursive approach works well:

def delete(self, word: str) -> bool:
    # Returns True if word existed and was deleted
    def _delete(node, word, depth):
        if depth == len(word):
            if not node.is_end:
                return False
            node.is_end = False
            # If no children, node can be removed
            return len(node.children) == 0
        char = word[depth]
        if char not in node.children:
            return False
        should_delete_child = _delete(node.children[char], word, depth + 1)
        if should_delete_child:
            del node.children[char]
            # Return True if current node has no other children and is not an end node
            return not node.is_end and len(node.children) == 0
        return False

    return _delete(self.root, word, 0)

This recursive function walks down, marks is_end as False at the word's end, then backtracks to delete nodes that have become leaf nodes and are not part of another word.

Common Interview Problems and Solutions

Now let’s tackle typical trie-based problems with complete solutions. Each problem includes the approach and code.

Problem 1: Autocomplete System

Description: Implement a function that, given a prefix, returns all words in the trie that start with that prefix, sorted lexicographically.

Solution: After reaching the node for the prefix, perform a DFS to collect all complete words.

class TrieWithAutoComplete(Trie):
    def get_words_with_prefix(self, prefix: str):
        node = self.root
        # Traverse to the end of prefix
        for char in prefix:
            if char not in node.children:
                return []  # prefix not found
            node = node.children[char]
        # Collect all words from this node
        result = []
        self._dfs_collect(node, prefix, result)
        return sorted(result)  # return lexicographically sorted

    def _dfs_collect(self, node, current_prefix, result):
        if node.is_end:
            result.append(current_prefix)
        for char, child_node in node.children.items():
            self._dfs_collect(child_node, current_prefix + char, result)

# Usage example
trie = TrieWithAutoComplete()
words = ["cat", "car", "cart", "dog", "cow", "carpet"]
for w in words:
    trie.insert(w)
print(trie.get_words_with_prefix("car"))  # ['car', 'carpet', 'cart']

Problem 2: Word Dictionary with Dot Wildcards

Description: Design a data structure that supports adding words and searching for words where dots '.' can match any letter. (LeetCode 211: Design Add and Search Words Data Structure).

Solution: Use a trie and for search with wildcards, perform a recursive DFS when encountering '.'.

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

    def add_word(self, word: str) -> None:
        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_word(self, word: str) -> bool:
        def dfs(node, index):
            if index == len(word):
                return node.is_end
            char = word[index]
            if char == '.':
                # try all possible children
                for child in node.children.values():
                    if dfs(child, index + 1):
                        return True
                return False
            else:
                if char not in node.children:
                    return False
                return dfs(node.children[char], index + 1)
        return dfs(self.root, 0)

# Example
wd = WordDictionary()
wd.add_word("bad")
wd.add_word("dad")
wd.add_word("mad")
print(wd.search_word("pad"))   # False
print(wd.search_word("bad"))   # True
print(wd.search_word(".ad"))   # True (matches bad, dad, mad)
print(wd.search_word("b.."))   # True (matches bad)

Problem 3: Count Words with a Given Prefix

Description: Given a list of words, answer queries about how many words start with a certain prefix. Preprocess with a trie storing a count at each node.

Solution: Each node stores a counter of how many words pass through it (including itself if it's an end).

class TriePrefixCounter:
    def __init__(self):
        self.root = TrieNodeWithCount()

class TrieNodeWithCount:
    def __init__(self):
        self.children = {}
        self.is_end = False
        self.prefix_count = 0  # number of words that have this prefix

class TrieWithCount:
    def __init__(self):
        self.root = TrieNodeWithCount()

    def insert(self, word: str):
        node = self.root
        node.prefix_count += 1
        for char in word:
            if char not in node.children:
                node.children[char] = TrieNodeWithCount()
            node = node.children[char]
            node.prefix_count += 1
        node.is_end = True

    def count_words_with_prefix(self, prefix: str) -> int:
        node = self.root
        for char in prefix:
            if char not in node.children:
                return 0
            node = node.children[char]
        return node.prefix_count

# Example
tc = TrieWithCount()
words = ["apple", "app", "ape", "bat", "ball"]
for w in words:
    tc.insert(w)
print(tc.count_words_with_prefix("ap"))  # 3 (apple, app, ape)
print(tc.count_words_with_prefix("ba"))  # 2 (bat, ball)

Problem 4: Longest Common Prefix Among a Set of Strings

Description: Find the longest common prefix among an array of strings using a trie. (LeetCode 14 variant).

Solution: Insert all strings, then traverse while there is exactly one child and no end node (or end node is fine if we allow that). Alternatively, a simple trie approach: walk from root, if at any node there is more than one child or we hit a word end before processing all strings, stop.

def longest_common_prefix(strs):
    if not strs:
        return ""
    # Insert all words into trie
    trie = Trie()
    for word in strs:
        trie.insert(word)
    # Walk from root as long as there is exactly one child and node is not end
    node = trie.root
    prefix = []
    while True:
        # If node is end, we stop (but if there are more characters in other strings, it's not common anyway)
        # We consider: stop if node has != 1 child or node.is_end is True
        if node.is_end or len(node.children) != 1:
            break
        # There is exactly one child, move to it
        char, child = next(iter(node.children.items()))
        prefix.append(char)
        node = child
    return ''.join(prefix)

# Example
print(longest_common_prefix(["flower","flow","flight"]))  # "fl"
print(longest_common_prefix(["dog","racecar","car"]))    # ""

Note: This trie-based approach works well, but a simpler approach without trie (comparing characters) is more space-efficient. However, it demonstrates trie usage.

Best Practices for Trie Problems in Interviews

When tackling trie problems, keep these tips in mind:

Conclusion

Tries are a fundamental data structure that shines in string-based retrieval tasks. In interviews, they allow you to solve problems with prefix matching, wildcard search, and autocomplete efficiently. By mastering the basic implementation and common variations, you'll be prepared to handle any trie-related question with confidence. Remember to practice writing clean, modular code and to analyze trade-offs between time and space. With the patterns and solutions covered here, you have a solid foundation to excel in your next coding interview.

πŸš€ Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started β€” $23.99/mo
← Back to all articles