Design Search Autocomplete System is a classic "design" trie problem. It is the engine behind the dropdown that appears under a search box: as you type each character, it instantly offers the most popular things people typed before that start with what you have so far.
Problem. Build a class with one method, input(c). Characters arrive one at a time. The special
character # ends the current sentence (and bumps its popularity count by one). For every other
character, return the top 3 historical sentences that start with everything typed so far, ranked by
how many times each was typed (the hot count), breaking ties lexicographically (smaller
string first).
Example history: 'i love you' typed 5 times, 'island' 3 times, 'i love coding' 2 times. Type
'i' then ' ' and the suggestions become ['i love you', 'i love coding'].
The slow way first
The brute-force idea: keep every past sentence in a list. On each keystroke, scan the whole list, filter to the ones starting with the current prefix, then sort them by count. If there are n stored sentences this is O(n) filtered work plus an O(n log n) sort on every single character. For a real search bar with millions of sentences that is hopeless.
The question to ask: the prefix only grows by one character at a time — can I reuse the work from the previous keystroke instead of rescanning everything? A trie does exactly that.
The idea: a trie where each node remembers its sentences
Store the history in a trie (prefix tree): one edge per character. The trick is that each node carries a small map of sentence -> count for every full sentence that passes through it. Now typing a character is just walking one edge down. The node you land on already holds every candidate that matches the prefix — no scanning. Rank that node's map by (count desc, then lexicographic) and take the first 3.
The key insight: the prefix never resets, so we never restart the walk from the root for nothing — each keystroke advances the same path one node deeper, and the candidate set only shrinks.
Walk through it
Step through the animation. We type 'i', then a space, then 'l'. Each character lights up one edge and moves us one node down. At each node the top-3 panel re-ranks the sentences stored beneath it: 'island' falls out the moment we type the space (it has no space in it), and the list keeps narrowing. When we type 'a' after 'i l', node l has no a child — the walk falls off the trie and we return an empty list.
Pseudocode
keyword = keyword + c # grow the typed prefix
node = root
for each character ch in keyword:
if ch is not a child of node:
return [] # nothing matches this prefix
node = node.children[ch]
candidates = every (sentence, count) stored at node
sort candidates by count descending, then sentence ascending
return the first 3 sentencesThe Python solution
def input(self, c):
self.keyword += c
node = self.root
for ch in self.keyword:
if ch not in node.children:
return [] # fell off the trie
node = node.children[ch]
# rank sentences stored under this node
cands = node.counts.items()
ranked = sorted(cands, key=lambda kv: (-kv[1], kv[0]))
return [s for s, _ in ranked[:3]]self.keywordaccumulates everything typed since the last#; we re-walk it from the root.- The
forloop walks one node per character; in practice you cachenodebetween calls so each keystroke is a single hop. - Line 5 to 6 is the fall-off case: if the current character has no edge, no stored sentence matches, so return
[]. node.countsis this node's map of every full sentence passing through it to its hot count.- The sort key
(-count, sentence)ranks by count descending, then sentence ascending for ties;ranked[:3]keeps the top 3.
Complexity
| Case | Time | Notes |
|---|---|---|
| Rescan list each keystroke | O(n log n) (moderate) | filter + sort all sentences |
| Walk to the node | O(p) (moderate) | p = prefix length, one hop per char |
| Rank that node | O(k log k) (moderate) | k = sentences under the node |
O(N) (moderate)Here N is the total length of all stored sentences (the trie size) and k is usually tiny — only the sentences sharing this prefix. We replace an O(n) rescan with a single edge walk plus a sort over a small candidate set.
When this pattern shows up
Whenever a problem feeds you characters or words and asks about prefixes — autocomplete, "starts with," word search, longest common prefix — reach for a trie. Storing extra data on each node (a count, a flag, a list) so a query becomes a single walk is the move that turns these from rescans into O(prefix) lookups.
Mind the tie-break. Ranking must be count descending but, for equal counts, sentence ascending.
A single sorted(key=lambda kv: (-kv[1], kv[0])) does both at once; sorting by count alone leaves ties
in an undefined order and fails the judge.
Practice
History: 'i love you'=5, 'island'=3, 'i love coding'=2. After typing 'i' then a space, which sentences remain and in what order?
1. Why does each trie node store a map of sentence to count?
2. How are suggestions ranked?
3. What happens when the next character has no edge in the trie?
4. Why is the trie faster than rescanning a list on every keystroke?