Back to System Design Problems
Q9Question 9 of 13

Design a Search Autocomplete / Typeahead

Key Concepts
  • A trie indexes query strings by prefix; walking the path for a prefix lands on the node for all completions.
  • Precompute and cache the top-K most popular completions at each trie node for near-constant-time results.
  • Popularity rankings come from query logs; update in batches to keep reads fast while staying fresh.
  • Cache the hottest prefixes (single letters) in a fast store so even the trie walk is skipped.
  • The trade-off is freshness vs read speed: a new trending query may take minutes to appear in suggestions.
  • Filter suggestions by user context and permissions so a customer never sees admin-only actions.
  • The trie walk costs only the length of the prefix, making it independent of vocabulary size.

Definition

Index queries in a trie by prefix, precompute top-K popular completions at each node, cache hot prefixes, and refresh rankings from query logs.
  • Trie : a tree where each edge is a character; walking a path spells a prefix, and the node represents all queries starting with it.
  • Top-K precomputation : storing the K most popular completions at each trie node for near-constant-time results.
  • Query log : the source of popularity data; each search increments a query's frequency.
  • Batched updates : periodically rebuilding top-K lists from recent logs to keep suggestions fresh without slowing reads.

When Rajesh types "tra" in the National Bank of India app, it instantly suggests "transfer money," "transaction history," and "track cheque." The app keeps a tree of letters where following t-r-a lands on a ready-made list of the best suggestions.

Understanding the Concepts

How does the trie work?

A trie is a tree where each edge is a character. Walking the path for a prefix lands on the node that represents all completions starting with those characters. The walk costs only the length of the prefix, making it independent of vocabulary size. At National Bank of India, thousands of searchable actions exist, but the trie finds matches for "tra" in microseconds.

JAVA
package com.bank.search;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;

public class TrieNode {
    Map<Character, TrieNode> children = new HashMap<>();
    List<String> topK = new ArrayList<>();
}

public class Trie {
    private final TrieNode root = new TrieNode();

    public TrieNode walk(String prefix) {
        TrieNode node = root;
        for (char c : prefix.toCharArray()) {
            node = node.children.get(c);
            if (node == null) return null;
        }
        return node;
    }
}

Why precompute top-K at each node?

Scanning every descendant of a prefix node would be too slow for real-time suggestions. By storing the K most popular completions directly at each node during a batch rebuild, answering a query becomes a simple lookup. The precomputed list at the "tra" node might hold "transfer money" (searched 50,000 times), "transaction history" (40,000), and "track cheque" (15,000).

JAVA
package com.bank.search;

import java.util.*;
import java.util.stream.Collectors;

public class TopKBuilder {
    public void rebuild(TrieNode node, Map<String, Long> frequencies) {
        node.topK = frequencies.entrySet().stream()
            .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
            .limit(10)
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());
    }
}

How do popularity rankings get updated?

Query logs are processed in batches by a background job. The job reads recent search events, updates frequency counts, and rebuilds the top-K lists at affected trie nodes. This keeps reads fast (no locking during updates) while staying reasonably fresh. At National Bank of India, the batch runs every five minutes.

JAVA
package com.bank.search;

import java.util.List;
import java.util.Map;
import java.util.HashMap;

public class QueryLogProcessor {
    public Map<String, Long> aggregate(List<String> recentQueries) {
        Map<String, Long> frequencies = new HashMap<>();
        for (String query : recentQueries)
            frequencies.merge(query, 1L, Long::sum);
        return frequencies;
    }
}

How does caching help?

The hottest prefixes (single letters like "t", "a", "s") are cached in Redis so even the trie walk is skipped. These prefixes account for the majority of requests because every search starts with a single character. The cache has a short TTL and is refreshed after each batch rebuild.

JAVA
package com.bank.search;

import java.util.List;

public class PrefixCacheService {
    public List<String> suggest(String prefix) {
        List<String> cached = redis.get("suggest:" + prefix);
        if (cached != null) return cached;  // cache hit, skip trie
        TrieNode node = trie.walk(prefix);
        if (node == null) return List.of();
        redis.setex("suggest:" + prefix, 300, node.topK);  // cache for 5 min
        return node.topK;
    }
}

What is the freshness trade-off?

Reads must be instant (under 100 ms), so a new trending query may take minutes to appear in suggestions. This is eventual consistency applied to rankings. If the bank launches a new "instant loan" feature and customers start searching for it, the suggestion appears after the next batch run, not immediately.

How do you filter by context?

Filter suggestions by user role and permissions so customers never see admin actions like "manage users" or "override limits." The trie stores all suggestions, and a post-filter removes ineligible ones before returning results to the client.

How does the search box interaction work?

On each keystroke, the client sends the current prefix and receives top-K suggestions within 50 to 100 milliseconds. The client debounces keystrokes to avoid flooding the server with requests for every character.

How do you handle multi-language input?

For non-Latin scripts like Hindi or Tamil, the trie indexes Unicode characters. The trie structure works the same way, but the character set is larger. Normalization (lowercasing, accent removal) is applied before insertion and lookup.

What should a senior engineer know?

A senior engineer considers sharding the trie by prefix range for horizontal scale, internationalization for non-Latin scripts, and personalization that blends global popularity with per-user query history. They also monitor suggestion click-through rates to validate ranking quality and detect stale or misleading suggestions.

Visual Overview

DIAGRAM
  Search Autocomplete Architecture

  ┌──────────┐  "tra"  ┌──────────────┐  HIT  ┌───────────────┐
  │  Client  │────────►│  Prefix      │──────►│ Top-K results │
  │  (typing)│         │  Cache       │       │ (cached)      │
  └──────────┘         │  (Redis)     │       └───────────────┘
                       └──────┬───────┘
                         MISS │
                       ┌──────▼───────┐
                       │  Trie Server │
                       │              │
                       │  root        │
                       │  └─t         │
                       │    └─r       │
                       │      └─a     │ ◄── walk to node
                       │        topK: │
                       │        [transfer money,   │
                       │         transaction history,│
                       │         track cheque]      │
                       └──────────────┘

  Background job:
  Query logs ──► batch process ──► update topK at each node

Key Takeaway

A search autocomplete indexes queries in a trie and precomputes top-K completions at each node for near-constant-time results. Popularity comes from query logs processed in batches. Hot prefixes are cached for even faster responses.

The trade-off is freshness vs speed: a new trending query may take minutes to appear, which is acceptable eventual consistency for rankings. Filter suggestions by context and permissions.

Remember one sentence: walk a trie to the prefix node, return precomputed top-K completions, update rankings in batches from query logs, and cache the hottest prefixes.

---

Share
Use ← → arrow keys to navigate