Last Updated: April 4, 2026
We're building a simple autocomplete feature. As the user types each character of searchWord, we need to find all products that share the same prefix as what's been typed so far, then return up to 3 of them in lexicographic (alphabetical) order.
So if the user types "m", we want the 3 alphabetically smallest products starting with "m". Then they type "o" (prefix is now "mo"), and we want the 3 smallest products starting with "mo". And so on for each character.
The key observation is that we want the lexicographically smallest matches, not the most popular or most relevant. This is different from the "Design Search Autocomplete System" problem. Because we want lexicographic order, sorting the products upfront gives us a huge advantage. In a sorted array, all products with a common prefix sit next to each other in a contiguous block. Finding that block efficiently is the core challenge.
products.length <= 1000 and total character count <= 2 10^4 -- The data set is moderate. Even an O(n m) brute force per character is feasible.searchWord.length <= 1000 -- We make up to 1000 queries (one per character). Combined with the product constraints, total work must stay well under ~10^8 operations.The most natural first attempt: for each prefix, scan through all products, collect the ones that start with that prefix, sort them, and grab the first 3. We already know the products and the prefixes (each prefix of searchWord), so this is straightforward.
The sorting is actually the wasteful part here. We re-sort candidates every single time a character is typed, even though the relative order of products never changes.
i from 0 to searchWord.length - 1, extract the prefix searchWord[0..i].Input:
For each prefix, we filter all products, sort matches, and take top 3:
The bottleneck is re-sorting matching products on every keystroke, even though the products never change order. What if we sorted once upfront and used binary search to find the matching range?
Here is the key insight: if we sort the products array once, all products sharing a prefix will be grouped together in a contiguous block. Searching for prefix "mo" means finding where "mo" would be inserted. Everything from that insertion point onward that still starts with "mo" is a match, and they are already in lexicographic order.
So the plan is simple. Sort once, then for each prefix use binary search to find the starting position. Check up to 3 products from that position to see if they match the prefix. Because the array is sorted, the first 3 matches we find are guaranteed to be the lexicographically smallest.
Sorting creates a property we can exploit: all strings sharing a prefix are adjacent. When we binary search for a prefix like "mo", the lower bound gives us the first position where a string >= "mo" would appear. Any product starting with "mo" must be at that position or immediately after it. So we only need to check 3 positions starting there. If the first candidate does not start with the prefix, none of the later ones will either.
products array lexicographically.i from 0 to searchWord.length - 1, form the prefix searchWord[0..i].prefix could be inserted (lower bound).The binary search approach works well, but we compute a new binary search for every character typed. Since the valid range can only shrink as the prefix grows, what if we tracked the range and narrowed it incrementally?
Building on the sorted array idea, here is an even cleaner approach. Instead of binary searching every time, we maintain two pointers, left and right, that define the range of products matching the current prefix. Initially, the entire sorted array is in range. As each character is typed, we shrink the range from both ends: move left forward past products that are too short or don't have the right character at the current position, and move right backward past products that don't match either.
This works because adding a character to the prefix can only eliminate products, never add new ones. So the range monotonically shrinks. Each pointer moves at most n total positions across all characters, giving us O(n) total pointer movement.
The sorted order guarantees that all products matching a given prefix form a contiguous block. Each new character can only eliminate products from the edges of this block (products that don't have the right character at the new position). So the two pointers only ever move inward, never outward. This means across all S characters, the left pointer moves at most n positions total, and the right pointer moves at most n positions total, giving us O(n) total pointer work on top of the initial sort.
products array lexicographically.left = 0 and right = products.length - 1.i from 0 to searchWord.length - 1:left <= right and (products[left].length <= i or products[left][i] != searchWord[i]), increment left.left <= right and (products[right].length <= i or products[right][i] != searchWord[i]), decrement right.left to min(left + 2, right).