Design autocomplete with Trie
Company: Google
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This question evaluates understanding of Trie-based string indexing, augmented per-node metadata for efficient prefix queries, and algorithmic analysis of insert, update, delete, and topK operations including their time and space complexity.
Constraints
- Words consist of lowercase English letters a-z only (branching factor <= 26).
- Weights are integers fitting in a machine word; ties broken by lexicographic order of the word.
- topK is the hot path; mutations are comparatively rare.
- Dictionary size: thousands to low millions of words, single-machine in-memory.
- insert on an existing word overwrites its weight; update/delete on a missing word is a no-op.
- topK with an empty prefix returns the global top-k; a prefix with no matches returns [].
Examples
Input: ([['insert', 'cat', 5], ['insert', 'car', 3], ['insert', 'cart', 8], ['insert', 'dog', 2], ['topK', 'ca', 2], ['update', 'car', 9], ['topK', 'ca', 2], ['delete', 'cart'], ['topK', 'ca', 2]],)
Expected Output: [['cart', 'cat'], ['car', 'cart'], ['car', 'cat']]
Explanation: The question's exact dry run. After the inserts, 'ca' subtree holds cart:8, cat:5, car:3, so topK('ca',2)=[cart,cat]. update('car',9) makes car the top, so topK=[car,cart]. delete('cart') leaves car:9, cat:5, so topK=[car,cat]. 'dog' lives in a different subtree and is never touched.
Input: ([['topK', 'ca', 3]],)
Expected Output: [[]]
Explanation: Querying a prefix on an empty dictionary: the descent hits a missing child immediately, so topK returns [].
Hints
- A Trie answers 'which words share this prefix?' for free: after walking the prefix, the node you land on roots the subtree of all matching words. The open question is getting top-k by weight out of that subtree without scanning it on every query.
- Propagate the best candidates up the spine: at each node cache a small sorted list of its subtree's top words keyed by (-weight, word). Then topK is just walk-to-prefix-node and read the cached list — O(P + k).
- A weight change or deletion can only affect the cached lists of ancestors on the path from the word's terminal node to the root. Re-derive each node's cache bottom-up along that path from its own word plus its children's caches; every node off the path stays valid.
- Include the node's OWN terminal word when rebuilding, not just children — an internal node can also be a complete word (e.g. 'car' is a prefix of 'cart').