AlgoMaster Logo

Search Suggestions System

Last Updated: April 4, 2026

medium
4 min read

Understanding the Problem

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.

Key Constraints:

  • 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.
  • Products consist of only lowercase English letters -- A Trie would need at most 26 children per node.

Approach 1: Brute Force (Filter and Sort Per Character)

Intuition

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.

Algorithm

  1. For each index i from 0 to searchWord.length - 1, extract the prefix searchWord[0..i].
  2. Scan all products and collect those that start with the current prefix.
  3. Sort the matching products lexicographically.
  4. Take the first 3 (or fewer if less than 3 match) and add to the result.
  5. Return the list of all suggestion lists.

Example Walkthrough

Input:

0
mobile
1
mouse
2
moneypot
3
monitor
4
mousepad
products
0
m
1
o
2
u
3
s
4
e
searchWord

For each prefix, we filter all products, sort matches, and take top 3:

  • Prefix "m": 5 products match, sort, take 3 -> ["mobile", "moneypot", "monitor"]
  • Prefix "mo": 5 products match, sort, take 3 -> ["mobile", "moneypot", "monitor"]
  • Prefix "mou": 2 products match -> ["mouse", "mousepad"]
  • Prefix "mous": 2 match -> ["mouse", "mousepad"]
  • Prefix "mouse": 2 match -> ["mouse", "mousepad"]
0
["mobile","moneypot","monitor"]
1
["mobile","moneypot","monitor"]
2
["mouse","mousepad"]
3
["mouse","mousepad"]
4
["mouse","mousepad"]
result

Code

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?

Approach 2: Sort + Binary Search

Intuition

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.

Algorithm

  1. Sort the products array lexicographically.
  2. For each index i from 0 to searchWord.length - 1, form the prefix searchWord[0..i].
  3. Use binary search to find the leftmost index where prefix could be inserted (lower bound).
  4. Starting from that index, check up to 3 products. If a product starts with the prefix, add it to the suggestions.
  5. Add the suggestions list to the result.

Example Walkthrough

1Sorted products. Prefix: "m" -> binary search -> index 0
0
mobile
start
1
moneypot
2
monitor
3
mouse
4
mousepad
1/5

Code

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?

Approach 3: Sort + Two Pointers (Optimal)

Intuition

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.

Algorithm

  1. Sort the products array lexicographically.
  2. Initialize left = 0 and right = products.length - 1.
  3. For each index i from 0 to searchWord.length - 1:
    • While left <= right and (products[left].length <= i or products[left][i] != searchWord[i]), increment left.
    • While left <= right and (products[right].length <= i or products[right][i] != searchWord[i]), decrement right.
    • Collect up to 3 products from left to min(left + 2, right).
    • Add them to the result.
  4. Return the result.

Example Walkthrough

1Initial: left=0, right=4. Full range.
0
left
mobile
1
moneypot
2
monitor
3
mouse
4
right
mousepad
1/6

Code