#208 Implement Trie (Prefix Tree)
the problem
In plain words
Build a class Trie with insert(word), search(word) (is this exact word stored?) and startsWith(prefix) (is any stored word starting with prefix?).
in insert("apple"), search("apple"), search("app"), startsWith("app"), insert("app"), search("app")
out null, true, false, true, null, true
"app" is only a prefix until it is inserted itself.
- 1 ≤ word.length, prefix.length ≤ 2000
- Lowercase English letters only
- At most 3·10⁴ calls in total
your turn
Try it before you peek
Read the examples, say the brute force out loud, then write code: here or on LeetCode ↗. Stuck for 20 minutes? Open one hint at a time.
Solve it in the browser
Design: implement every operation. Compiled by the real javac (Java 17) and judged right here — nothing is sent anywhere.
Solutions are hidden so you can try first. Even 20 minutes of real effort makes the answer stick far better than reading it.
Marking the problem solved, or opening every hint, also shows it.
the aha
Key insight
patternTrie (prefix tree)
Each node stands for a prefix. A path that exists means the prefix exists; an end-of-word flag at the last node means the whole word exists.
solutions
From brute force to optimal
Store the words in a set; for startsWith, scan every word.
- insert → set.add(word).
- search → set.contains(word).
- startsWith → loop over all words and test
w.startsWith(prefix).
Hashing reads the word once; a prefix has no bucket, so every word must be checked.
The words themselves, S = total letters.
class SetTrie { private final Set<String> words = new HashSet<>(); public void insert(String word) { words.add(word); } public boolean search(String word) { return words.contains(word); } public boolean startsWith(String prefix) { for (String w : words) { // scans every stored word if (w.startsWith(prefix)) return true; } return false; }}Walk one node per letter; create missing nodes on insert; check the end flag on search.
- Node =
Node[26] children+boolean isEnd. - insert: from the root, for each letter create the child if missing and step into it; set
isEndat the end. - walk(s): follow letters, return null if a child is missing.
- search = walk(word) != null && isEnd; startsWith = walk(prefix) != null.
One array lookup per letter, independent of how many words are stored.
Up to one node per inserted letter, each holding 26 references.
class Trie { private static class Node { Node[] children = new Node[26]; // one slot per letter 'a'..'z' boolean isEnd; // true if a word ends exactly here } private final Node root = new Node(); public void insert(String word) { Node cur = root; for (char ch : word.toCharArray()) { int i = ch - 'a'; if (cur.children[i] == null) cur.children[i] = new Node(); cur = cur.children[i]; } cur.isEnd = true; } public boolean search(String word) { Node node = walk(word); return node != null && node.isEnd; } public boolean startsWith(String prefix) { return walk(prefix) != null; } /** Follows s letter by letter; null as soon as a letter is missing. */ private Node walk(String s) { Node cur = root; for (char ch : s.toCharArray()) { cur = cur.children[ch - 'a']; if (cur == null) return null; } return cur; }}Dry run
Same algorithm, but each node maps only the letters it actually has.
- children = new HashMap<Character, Node>().
- insert uses computeIfAbsent; walk uses get.
One hash lookup per letter.
Only existing edges are stored (but each map entry is a heavier object).
class MapTrie { private static class Node { Map<Character, Node> children = new HashMap<>(); // only letters that exist boolean isEnd; } private final Node root = new Node(); public void insert(String word) { Node cur = root; for (char ch : word.toCharArray()) { cur = cur.children.computeIfAbsent(ch, k -> new Node()); } cur.isEnd = true; } public boolean search(String word) { Node node = walk(word); return node != null && node.isEnd; } public boolean startsWith(String prefix) { return walk(prefix) != null; } private Node walk(String s) { Node cur = root; for (char ch : s.toCharArray()) { cur = cur.children.get(ch); if (cur == null) return null; } return cur; }}in the room
How to say it in the interview
talk track
- Clarify the alphabet (lowercase a–z?) — it decides array vs map.
- Draw "apple" and "app" sharing a path, and point at why
isEndis needed. - Write
walk()once and reuse it — interviewers notice the lack of duplication. - Close with complexity: O(L) per call, O(S·26) memory, and the map alternative.
traps
searchreturning true for a prefix (missing theisEndcheck).- Allocating
new Node[26]for the root but forgetting to create child nodes before stepping into them (NullPointerException).
what they ask next
Follow-ups
Implement Trie II (LC 1804): count words equal to / starting with, and erase?
Add pass and end counters to each node — see the counting trie in this chapter. All operations stay O(L).
How would you save memory?
Use a map per node, or a radix (compressed) trie where chains of single-child nodes become one edge labelled with a string.
before you submit
Edge cases to test
- search for a prefix of a stored word ("app" when only "apple" is stored) → false.
- startsWith with a whole stored word → true: a word is its own prefix.
- Inserting the same word twice → nothing changes; it is still found.
- A 2000-letter word → 2000 nodes on one path; a loop handles it without recursion.
practise next
Similar problems
- #211Design Add and Search Words Data StructureMediumin Tries
- #1268Search Suggestions SystemMediumin Tries
- #648Replace WordsMediumin Tries