Skip to content

Design Add and Search Words Data Structure

Design a data structure that supports adding words and searching for words with support for the ’.’ wildcard character.

Implement the WordDictionary class:

  • addWord(word) — Adds word to the data structure without duplicates.
  • search(word) — Returns true if there is any word in the data structure that matches word. word may contain dots ’.’ where each dot can represent any single letter.
OperationInputOutputState
AddWord”bad”Trie has: b→a→d
AddWord”dad”Trie has: d→a→d, b→a→d
AddWord”mad”Trie has: m→a→d, d→a→d, b→a→d
Search”pad”falseNo word starting with p
Search”bad”trueExact match found
Search”.ad”true’.’ matches ‘b’, ‘d’, or m
Search”b..“true’.’ matches ‘a’, then d
  • 1 <= word.length <= 500
  • word in addWord consists of lowercase English letters.
  • word in search consists of lowercase English letters and dots ’.’.
  • At most 5000 calls will be made to addWord and search.
ApproachAddWordSearchSpace
Recursive DFSO(m)O(n × 26^m)O(m × 26)
Iterative BFSO(m)O(n × 26^m)O(m × 26)

m = word length, n = number of words, search worst-case with many wildcards

  • Pick Recursive DFS for interview clarity — easier to explain the backtracking logic.
  • Pick Iterative BFS if you prefer avoiding recursion or have deep trees.
Clearer Logic
Recursive DFS
O(m) insert · O(n×26^m) search
Avoids Recursion
Iterative BFS
O(m) insert · O(n×26^m) search
★ Recommended

Use a Trie for storage. For each search:

  • If we encounter an exact letter, move to that child.
  • If we encounter ’.’, recursively try all 26 children.
  • Base case: reached end of word → check if it’s marked as end-of-word.
⏱ Time O(n×26^m) Worst-case backtracking 💾 Space O(m×26) Trie nodes
class WordDictionary:
root = TrieNode()
function addWord(word):
node = root
for each char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.is_end_of_word = True
function search(word):
return searchDFS(word, 0, root)
function searchDFS(word, index, node):
if index == len(word):
return node.is_end_of_word
char = word[index]
if char == '.':
for each child in node.children.values():
if searchDFS(word, index + 1, child):
return True
return False
else:
if char not in node.children:
return False
return searchDFS(word, index + 1, node.children[char])
design_search_words_recursive.py
class TrieNode:
def __init__(self):
self.children = {}
self.is_end_of_word = False
class WordDictionary:
def __init__(self):
self.root = TrieNode()
def addWord(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_of_word = True
def search(self, word: str) -> bool:
return self._search_dfs(word, 0, self.root)
def _search_dfs(self, word: str, index: int, node: TrieNode) -> bool:
if index == len(word):
return node.is_end_of_word
char = word[index]
if char == '.':
for child in node.children.values():
if self._search_dfs(word, index + 1, child):
return True
return False
else:
if char not in node.children:
return False
return self._search_dfs(word, index + 1, node.children[char])
# Example usage
wd = WordDictionary()
wd.addWord("bad")
wd.addWord("dad")
wd.addWord("mad")
print(wd.search("pad")) # False
print(wd.search("bad")) # True
print(wd.search(".ad")) # True
print(wd.search("b..")) # True
🎯 Interview Favourite

Maintain a queue of (node, index) pairs. For each step:

  • If exact character, add (child_node, index+1) to queue.
  • If ’.’, add all children with (child_node, index+1).
  • Base case: reach end of word and check end-of-word flag.
⏱ Time O(n×26^m) Worst-case all branches 💾 Space O(m) Queue size
design_search_words_iterative.py
from collections import deque
class TrieNode:
def __init__(self):
self.children = {}
self.is_end_of_word = False
class WordDictionary:
def __init__(self):
self.root = TrieNode()
def addWord(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_of_word = True
def search(self, word: str) -> bool:
queue = deque([(self.root, 0)])
while queue:
node, index = queue.popleft()
if index == len(word):
if node.is_end_of_word:
return True
continue
char = word[index]
if char == '.':
for child in node.children.values():
queue.append((child, index + 1))
else:
if char in node.children:
queue.append((node.children[char], index + 1))
return False
# Example usage
wd = WordDictionary()
wd.addWord("bad")
wd.addWord("dad")
wd.addWord("mad")
print(wd.search("pad")) # False
print(wd.search("bad")) # True
print(wd.search(".ad")) # True
print(wd.search("b..")) # True
✓ Simple

A third approach demonstrating an alternative technique for solving this problem. This implementation optimizes either time or space complexity compared to the previous approaches.

⏱ Time O(n) Optimized complexity 💾 Space O(1) Efficient space usage
function approach3():
// Implementation approach goes here
design_add_and_search_words_data_structure_space_optimized.py
"""
Solution for Design Add and Search Words Data Structure
"""
def solve():
"""Implementation for approach 3 of Design Add and Search Words Data Structure"""
pass
if __name__ == "__main__":
print("Solution ready")