System Design: Search Autocomplete

System DesignSearchBackendArchitecture
Share on LinkedIn

Autocomplete is a search system compressed into a keystroke budget. Users expect suggestions before they finish typing "blu" — ranked, spelled correctly, and irrelevant noise hidden. Netflix, Amazon, and Google trained everyone that slow or dumb suggestions mean the product is broken, even when full search works fine.

The design interview version asks for millions of queries, personalized ranking, and 50ms p99. The production version adds typo tolerance, fresh content indexing, A/B tests on ranking weights, and the reality that 80% of traffic hits the same few thousand prefixes.

Requirements framing

Functional:

Non-functional:

High-level architecture

[Client] --debounced--> [API Gateway] --> [Suggest Service]
                                              |
                    +-------------------------+------------------+
                    |                         |                  |
              [Redis cache]           [Elasticsearch]    [Ranking service]
              hot prefixes              completion index     CTR weights

Write path: catalog changes → stream to indexer → update completion FST / trie → invalidate cache keys for affected prefixes.

Read path: prefix → cache lookup → on miss, query index → apply ranking → cache result → return.

Index options

Approach Pros Cons
In-memory trie Microsecond lookup Memory, update complexity
ES completion suggester Built-in, FST compressed ES ops burden
Prefix + edge n-gram Flexible matching Heavier index size
Dedicated SaaS (Algolia) Fast to ship Cost, less control

Elasticsearch completion field example:

PUT products
{
  "mappings": {
    "properties": {
      "suggest": {
        "type": "completion",
        "contexts": {
          "category": { "type": "category" }
        }
      }
    }
  }
}
POST products/_search
{
  "suggest": {
    "product-suggest": {
      "prefix": "blu",
      "completion": {
        "field": "suggest",
        "size": 10,
        "contexts": { "category": "electronics" }
      }
    }
  }
}

Context filtering (category, locale, tenant) is essential for multi-tenant catalogs.

Ranking beyond alphabetically first

Precomputed popularity weights attached to each suggestion entry:

"bluetooth speaker" weight: 942
"blue ray player"   weight: 12

At query time, merge index order with:

Offline job computes prefix → [term, score] tables for top 100k prefixes; long tail hits ES directly.

Caching strategy

Key: suggest:{locale}:{category}:{prefix} with TTL 5–15 minutes. Invalidate on index update for affected terms. Popular prefixes ("iph", "sam") stay permanently warm.

Thundering herd on cache miss: single-flight rebuild per prefix key.

Client-side concerns

let controller;
function onInput(prefix) {
  clearTimeout(debounceTimer);
  debounceTimer = setTimeout(async () => {
    controller?.abort();
    controller = new AbortController();
    const res = await fetch(`/api/suggest?q=${encodeURIComponent(prefix)}`, {
      signal: controller.signal,
    });
    renderSuggestions(await res.json());
  }, 200);
}

Debounce 150–300ms. Abort stale requests. Keyboard navigation (arrows, enter) without extra round trips.

Failure and degradation

If suggest service fails, return empty array — do not block page. Log miss rate. Full search still works. Consider static fallback for top queries during outages.

Scale numbers (order of magnitude)

Sharding ES by locale or tenant. Redis cluster for cache.

Observability for suggest services

Track p50/p95 latency per prefix length, cache hit rate, and zero-result rate. Spikes in zero-results often mean index drift or a bad deploy — not user behavior change. Log anonymized prefix + selected suggestion position for offline ranking tuning without storing raw queries tied to users if policy restricts it.

Ranking signals for autocomplete

Blend signals with tunable weights:

score = 0.4 * prefix_match + 0.3 * popularity + 0.2 * recency + 0.1 * personalization

Debounce client input 150ms. Cache top queries in Redis — "lap" → precomputed suggestions. Protect against empty-prefix browse overload with rate limits.

Common production mistakes

Teams get search autocomplete wrong in predictable ways:

System design for search autocomplete breaks at scale when hot keys, thundering herds, and cache stampedes are discovered during launch week instead of load test week.

Debugging and triage workflow

When search autocomplete misbehaves in production, work top-down instead of guessing:

  1. Confirm scope — one tenant, region, or deployment stage? Narrow blast radius before deep diving.
  2. Check recent changes — deploys, flag flips, config pushes, and schema migrations in the last 24 hours.
  3. Compare golden signals — latency, error rate, saturation, and traffic for the affected surface vs. baseline.
  4. Reproduce minimally — smallest input or scenario that triggers the failure; capture traces/logs with correlation IDs.
  5. Fix forward or rollback — if rollback is faster than root-cause during incident, rollback first, postmortem second.
  6. Add a guard — alert, integration test, or circuit breaker so the same class of failure is caught earlier next time.

Document the timeline during triage. Future you (and on-call) will need timestamps, not just conclusions.

Metrics worth dashboarding

For search autocomplete, alert on symptoms users feel—not only infrastructure CPU:

Signal Why it matters
p95/p99 latency Tail latency drives timeouts and retries upstream
Error rate by operation Separates transient blips from systemic failure
Saturation (pool, queue, disk) Shows how close you are to hard limits
Business counter (success/failure) Ties technical metrics to revenue or task completion

Slice by version, region, and tenant during rollout. A flat global graph hides a bad canary.

Resources

Frequently asked questions

What data structure powers autocomplete?

Prefix trees (tries) are the classic structure — each node represents a character and paths form indexed terms. Production systems often use specialized indexes instead: Elasticsearch completion suggester (FST-based), prefix queries on edge n-grams, or dedicated services like Algolia. The goal is sub-millisecond prefix lookup on millions of terms.

How do you rank autocomplete suggestions?

Rank by blended signals: prefix match quality (exact prefix beats fuzzy), historical click-through rate, search frequency, recency, personalization (user history), and business rules (boost sponsored terms). Most systems precompute top-K suggestions per prefix offline and serve from cache at query time.

What latency budget should autocomplete target?

Target p95 under 100ms end-to-end, with the server portion under 30ms. Users type fast — slower suggestions feel broken or show stale prefixes. Debounce client requests (150–300ms), cancel in-flight requests on new keystrokes, and serve from CDN or edge cache for popular prefixes when possible.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →