Trie Pattern
Search any prefix in O(m). No matter how many words you've stored.
Quick Summary
Best used when
- ✓You need to search for strings by prefix, autocomplete, word suggestions
- ✓You're checking if any stored word starts with a given prefix
- ✓Many strings in the input share common prefixes
- ✓You need O(m) prefix lookup where m is the word length, not O(n) where n is the dictionary size
Clue words in the problem
Complexity gain
O(n × m) repeated string scanning → O(m) prefix lookup regardless of dictionary size
Why this pattern matters for LeetCode
A hash map of words tells you if an exact word exists in O(1), but it can't tell you if any word starts with a given prefix without scanning every key. A trie stores strings character by character in a tree, so all words sharing a prefix share the same path from the root. Prefix lookups take O(m) time, the length of the prefix, not the size of the dictionary. This is why autocomplete, spell-check, and word search problems reach for tries.
What the Trie pattern is
A trie (prefix tree) is a tree where each node represents one character. Each path from the root to a terminal node spells a complete word. To insert a word, walk character by character from the root, creating nodes for characters that don't yet exist, and mark the final node as a word ending. To search, walk the same path and check if the final node is marked as a word end. To check a prefix, walk the path without requiring the end node to be marked, if the path exists, the prefix exists.
Reach for Trie when you catch yourself thinking…
These are the internal questions that signal this pattern.
Beginner mental model
Think of a filing cabinet where each drawer is one letter. The 'a' drawer holds all words that start with 'a'. Inside it are sub-drawers for the second letter: 'ap', 'ar', 'at', etc. To find 'apple', you open 'a', then 'ap', then 'app', 'appl', 'apple', and check if that last folder is marked 'complete word.' You never look at words that start with a different letter. That's what makes prefix lookups O(m) instead of O(n × m).
Ready to practice Trie?
Guided hints, not answers. You build real intuition.
When to use Trie
- ✓Implement Trie, build insert, search, and startsWith from scratch
- ✓Search Suggestions System, return words sharing a prefix for autocomplete
- ✓Word Search II, find all words from a dictionary in a grid (trie + DFS)
- ✓Replace Words, replace words with their shortest dictionary root prefix
- ✓Design Add and Search Words, search with wildcard '.' characters
When NOT to use Trie
- ✕You only need exact word lookup with no prefix queries, a Set or hash map is simpler
- ✕The input set is tiny, a linear scan of an array is clearer and fast enough
- ✕You need fuzzy matching (edit distance) rather than exact prefix matching, different algorithm
- ✕Memory is severely constrained, tries use O(n × m × alphabet_size) in the worst case
Better alternative: In those cases, a hash map or Set handles exact lookups in O(1), and Levenshtein distance handles fuzzy matching.
JavaScript code template
Implement Trie. O(m) per insert, search, and startsWith
class TrieNode {
constructor() {
this.children = {};
this.isEnd = false;
}
}
class Trie {
constructor() {
this.root = new TrieNode();
}
insert(word) {
let node = this.root;
for (const char of word) {
if (!node.children[char]) {
node.children[char] = new TrieNode();
}
node = node.children[char];
}
node.isEnd = true;
}
search(word) {
let node = this.root;
for (const char of word) {
if (!node.children[char]) return false;
node = node.children[char];
}
return node.isEnd; // path exists AND it ends a complete word
}
startsWith(prefix) {
let node = this.root;
for (const char of prefix) {
if (!node.children[char]) return false;
node = node.children[char];
}
return true; // path exists, prefix is present
}
}Each TrieNode holds a children object (character → child node) and an isEnd flag. Insert walks character by character, creating new nodes where needed, and marks the last node as a word end. Search walks the same path and requires isEnd = true at the final node, a path that exists but isn't marked means the search string is only a prefix of a stored word, not a word itself. startsWith is identical to search except it doesn't check isEnd.
Step-by-step thinking process
Walk through this in your head before writing code.
- 1Create a root TrieNode, the empty-string starting point.
- 2For insert: walk character by character from the root, creating new TrieNode children where none exist.
- 3After the last character of insert, set node.isEnd = true to mark a complete word.
- 4For search: walk character by character. If any character is missing from children, return false. At the end, return node.isEnd.
- 5For startsWith: same walk as search, but return true regardless of isEnd, if the path exists, the prefix exists.
“This app finally made it click. I tried NeetCode, CTCI, and YouTube for years and still couldn't solve Two Sum. The beginner mental models for the patterns are genuinely so helpful. THANK YOU.”
LeetCode-style problems that use this pattern
Practice on DSA Trainer with guided hints: no spoilers, just nudges.
Coming soon
- Implement Trie (Prefix Tree)Coming soon
Build insert, search, and startsWith from scratch, the canonical trie problem.
- Search Suggestions SystemComing soon
Insert all products into a trie. For each prefix of the search term, collect up to 3 lexicographically smallest matches.
- Word Search IIComing soon
Build a trie from the word list, then DFS the grid. Prune DFS branches that don't match any trie prefix.
- Design Add and Search WordsComing soon
Like Implement Trie, but search must handle '.' wildcards, recurse into all children when you hit a dot.
Common mistakes beginners make
- ✕Confusing search and startsWith, search requires isEnd = true at the last node; startsWith does not. A string that is only a prefix of a stored word returns false for search and true for startsWith.
- ✕Not checking if the child node exists before accessing it, if a character is missing, node.children[char] is undefined and accessing its children crashes.
- ✕Building a trie when a hash set would do, if the problem only needs exact-word lookup with no prefix queries, a Set is simpler and uses less memory.
- ✕Using an array of 26 instead of a hash object and not handling the index correctly, an array works for lowercase English letters but requires char.charCodeAt(0) - 97 arithmetic. A plain object {} is simpler and works for any character set.
Trie vs Hash Map vs Set
Choose the right tool for the job.
| Pattern | Use when… |
|---|---|
| Trie | Prefix queries, startsWith checks, autocomplete, or any problem needing fast lookup by the beginning of a word |
| Hash Map / Set | Exact-word lookup only, no prefix queries needed. O(1) average lookup, simpler to implement |
| Linear scan of array | Dictionary is tiny (under ~100 words) and prefix queries are infrequent, simplicity beats the trie overhead |
Time and space complexity
Time complexity
O(m) per insert, search, or startsWith
Space complexity
O(n × m)
Each operation walks at most m characters. O(m) per call. Space is proportional to the total number of characters stored across all words. In the worst case, n words of average length m with no shared prefixes require n × m nodes. In practice, shared prefixes reduce this significantly.
Ready to practice Trie?
Guided hints, not spoilers. 54 problems across 23 patterns. Five are free, the rest unlock with a founding member seat.
Frequently asked questions
- When should I use a trie instead of a hash set?
- Use a trie when the problem involves prefix queries, checking if a word starts with a given prefix, or collecting all words that share a prefix. A hash set can't answer prefix queries without scanning every key. If the problem only needs exact word lookup, a hash set is simpler and uses less memory.
- Why is startsWith O(m) and not O(n)?
- Because you're walking the trie path for the prefix, m characters, m node hops. The number of words in the trie (n) doesn't matter. You never look at words that diverged at a different character. This is the trie's key advantage over scanning a list of words.
- What's the difference between isEnd and not isEnd?
- Both 'app' and 'apple' share the path a → p → p. If you only inserted 'apple', the node at the second 'p' has isEnd = false (it's a prefix, not a complete word). If you also inserted 'app', that node has isEnd = true. search('app') returns true only if that node's isEnd is true. startsWith('app') returns true regardless.
- Do I always implement Trie from scratch in interviews?
- If the problem is literally 'Implement Trie,' yes. For problems that use a trie as a tool (Word Search II, Search Suggestions), you implement the trie as part of the broader solution. JavaScript has no built-in trie, so you'll always write the TrieNode class and the insert/search/startsWith methods yourself.