Prefix search & autocomplete
In one line
Walk the trie down to the node for the typed prefix, then explore only that node's subtree to list, count or rank the words that start with it.
What it is
Prefix search means answering 'which stored strings start with this prefix?'. In a trie, every word that starts with a prefix lives in the subtree under the prefix's last node, so the search has two phases: walk down the prefix in O(P) steps, then collect results from that subtree only.
Autocomplete is prefix search with a limit and an order: return the top k suggestions, either alphabetically or by popularity. Alphabetical order comes for free from a pre-order DFS that visits children in sorted order. Popularity order needs extra data, such as a frequency on each word, or a precomputed list of the best k words cached on every node.
Why interviewers ask
Autocomplete is one of the most common 'design a feature' questions, and 'Search Suggestions System' (LeetCode 1268) and 'Design Search Autocomplete System' (LeetCode 642) are popular coding versions. Interviewers check that you know the subtree trick, can cap the work at k results, and can discuss the trade-off between computing suggestions on each request and caching top-k lists in the nodes.
How it works
- 1Build the trie from the dictionary. Store the full word (and its frequency if ranking matters) on its end node, so you do not have to rebuild the string during the DFS.
- 2For a query prefix, walk from the root one character at a time. If a character is missing, no word has this prefix: return an empty list.
- 3From the prefix node, run a DFS. Visit the node itself first (pre-order) and add its word if it ends one; then visit children in sorted character order. This yields words in alphabetical order.
- 4Stop the DFS as soon as you have k results, so the cost is bounded by the size of the part of the subtree you actually touch, not the whole dictionary.
- 5For popularity ranking at scale, precompute at each node the top k words in its subtree (by frequency). A query then becomes 'walk the prefix and return the cached list' in O(P + k) time, at the cost of extra memory and work on updates.
Worked example · Suggestions for 'mo' and 'mou' with k = 3
- Dictionary: mobile, mouse, moneypot, monitor, mousepad. All five share the path m -> o.
- Under 'o' the children are b (mobile), n (moneypot, monitor) and u (mouse, mousepad).
- Query 'mo': walk m -> o. DFS visits children in order b, n, u.
- b branch: reach the end of 'mobile', out = [mobile].
- n branch: next node is e (moneypot) before i (monitor): out = [mobile, moneypot, monitor]. We now have k = 3, so the DFS stops and never enters the u branch.
- Query 'mou': walk m -> o -> u. DFS: s -> e reaches 'mouse' (end), add it, then continue to p -> a -> d and add 'mousepad'. out = [mouse, mousepad], only 2 results exist.
- Note that 'mouse' comes before 'mousepad' because pre-order adds a node's own word before its children's words, which matches alphabetical order.
Picture it
It is like a library shelf sorted by title: to find every book starting with 'Har', you walk straight to the 'Har' section and read along the shelf until you have enough, instead of checking every book in the building.
Reference code
class Node:
def __init__(self):
self.children = {}
self.word = None # the full word, stored at its last node
def build(words):
root = Node()
for w in words:
node = root
for ch in w:
node = node.children.setdefault(ch, Node())
node.word = w
return root
def suggest(root, prefix, k=3):
node = root
for ch in prefix: # 1. walk down to the prefix node
node = node.children.get(ch)
if node is None:
return []
out = []
def dfs(n): # 2. pre-order DFS = alphabetical order
if len(out) == k:
return
if n.word is not None:
out.append(n.word)
for ch in sorted(n.children):
dfs(n.children[ch])
dfs(node)
return outbuild stores each full word on its end node. suggest walks to the prefix node, then runs a pre-order DFS with sorted children, which yields alphabetical order, and stops at k results. Time is O(P) for the walk plus the nodes visited until k words are found.
Key patterns
Search suggestions (alphabetical top k)
When: LeetCode 1268: after each typed character, return up to 3 words
How: Trie plus DFS in sorted order capped at k; or keep the walk going character by character and reuse the current node.
Cached top-k per node
When: Many queries, ranking by frequency, few updates
How: Store the best k words at every node; a query is walk + return, updates refresh lists along one path.
Prefix counts
When: Count words with a given prefix (or sum values, LeetCode 677 Map Sum Pairs)
How: Keep a counter or running sum on each node, updated during insert.
Sorted array + binary search alternative
When: A static list where you want minimal memory
How: Sort the words, binary search for the first word >= prefix, then read forward while words still start with the prefix.
Real-world use
Search engines and shops
Search boxes on sites like Amazon or Google show popular completions as you type; the general approach is a prefix index with precomputed top suggestions per prefix, refreshed from query logs, so each keystroke is a lookup rather than a scan.
Elasticsearch completion suggester
The completion suggester builds an in-memory FST (a compressed trie-like automaton) per index segment, with weights per suggestion, so prefix suggestions come back fast and ranked by weight.
IDEs and shells
Code completion in editors and tab completion of commands and file names in shells narrow a sorted set of candidates by the typed prefix, which is the same prefix-search idea.
Phone keyboards
Mobile keyboards suggest the next word or completion from a dictionary organised for fast prefix lookups, combined with a language model to rank candidates.
In a project
You are adding an autocomplete box to an e-commerce site with 2 million product names; suggestions must appear in under 50 ms and be ranked by how often each product was searched last week.
- Each night, compute search counts per product name from the query logs.
- Build a trie of lowercased names, and at each node store the top 10 names in its subtree by count (merge children's lists bottom-up).
- Serve queries by walking the prefix and returning the cached list; cap prefixes at, for example, 20 characters.
- Put a CDN or in-memory cache in front for the most common short prefixes, since 'a', 'ip' and 'sh' are typed by everyone.
- Ship the new trie atomically (build a new copy, then swap the pointer) so users never see a half-built index.
Impact: Each keystroke costs O(prefix length) instead of a database LIKE 'prefix%' query, p99 latency drops to a few milliseconds, and suggestions reflect real demand, which typically raises click-through on the search box.
Trade-offs
Use it when
- You answer many prefix queries against a dictionary that changes rarely
- Results must appear per keystroke with low latency
- You need both alphabetical listing and counting by prefix
Avoid it when
- The list is static and memory matters: a sorted array with binary search gives prefix ranges with almost no overhead
- You need fuzzy matching (typos, middle-of-word matches): use an n-gram index or a search engine
- Rankings change every second: cached top-k lists per node become expensive to keep fresh
Cost: Walk O(P) for a prefix of length P. DFS collection is bounded by the nodes visited before finding k words (worst case the whole subtree). With cached top-k lists per node a query is O(P + k), but memory grows by k entries per node.
Common mistakes
- Running the DFS over the whole trie instead of only the prefix node's subtree.
- Not stopping at k results, so a one-letter prefix explores the entire dictionary.
- Iterating children in insertion order (an unsorted dict) and expecting alphabetical output.
- Rebuilding each word character by character in the DFS with string concatenation, which adds O(L) per step; store the word on the end node or use a list as a path.
- Ignoring case and whitespace normalisation, so 'iPhone' and 'iphone' end up in different branches.
Interview questions
Quick revision
- All words with prefix p live in the subtree of p's node.
- Phase 1: walk the prefix, O(P). Phase 2: DFS the subtree, stop at k.
- Pre-order DFS with sorted children = alphabetical order.
- Store the full word on its end node to avoid rebuilding strings.
- Popularity: cache top-k per node, query O(P + k), update along one path.
- Static list alternative: sort + binary search for the prefix range.
- Normalise input (lowercase, trim) before inserting and querying.
Check yourself
1. Words: mobile, moneypot, monitor, mouse, mousepad. What are the first 3 suggestions for 'mo' in alphabetical order?
2. Why does a pre-order DFS return 'mouse' before 'mousepad'?
3. Each node caches the top 5 words in its subtree. What does a query for a prefix of length P cost?
4. What is the main cost of caching top-k lists on every node?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.