skip to content

What do you trade away by indexing edge n-grams to power prefix autocomplete?

level: seniorimportance: should knowfreq 45%

answer

  1. Every prefix of every token becomes a term
  2. Work moves from query time to index time
  3. One-character grams live in nearly every document
  4. The two sides must not be analyzed the same way
  5. The length range is frozen into the postings

basics

~20 s

Edge n-grams index every prefix of every token so a prefix search becomes a plain term lookup. The cost is a multiplied index and term dictionary, distorted term statistics, and a full reindex whenever the gram length range changes.

solid answer

~50 s

An edge n-gram filter emits every prefix of a token within a configured length range — `shoes` becomes `s`, `sh`, `sho`, `shoe`, `shoes`. The payoff is that as-you-type search stops being a wildcard or term-dictionary scan and becomes a single exact term lookup, which is both fast and cheap to combine with filters. The costs are real: each token expands into several terms, so postings volume, term dictionary size and merge work all multiply; short grams like `s` appear in nearly every document, so their IDF collapses and term-frequency-based ranking gets noisy; and the query side must **not** be n-grammed, or a three-letter query would emit its own prefixes and match everything. The range is baked into the index, so widening it means reindexing. Alternatives worth naming are a prefix scan over the sorted term dictionary and a purpose-built finite-state suggester structure built from popular queries.

code

text · 9 lines
text
// index-time: edge n-grams, length 1..5
"shoes" -> [s] [sh] [sho] [shoe] [shoes]

// query-time: plain token, NOT n-grammed
"sho"   -> [sho]            => matches the indexed gram, 1 lookup

// query-time WITH n-grams (the bug)
"shoes" -> [s] [sh] [sho] [shoe] [shoes]
           the [s] clause matches nearly every document

go deeper

for a junior

Know what an edge n-gram is — every prefix of a token indexed as its own term — and that it makes as-you-type search a plain term lookup instead of a scan.

for a middle

Explain the cost model: several terms per token, larger dictionary and merges, and the mandatory query-side asymmetry. Be able to say why short grams wreck term statistics.

for a senior

Weigh the alternatives against corpus size and prefix length, put grams on a dedicated field, keep scoring off it, and plan for the reindex a range change forces. Diagnose the max-length cliff from symptoms.

for a principal

Own suggest as a product surface: whether completions come from corpus vocabulary or query demand, what latency and index-cost budget it gets, and how the suggest path is versioned independently of the main content index.

## N-grams and edge n-grams An **n-gram** filter emits every contiguous substring of a token within a length range: `shoe` with a range of 2–3 yields `sh`, `ho`, `oe`, `sho`, `hoe`. An **edge n-gram** filter emits only the substrings anchored at the start: `shoe` with range 1–4 yields `s`, `sh`, `sho`, `shoe`. Edge grams exist to serve prefix matching; full grams exist to serve infix/substring matching and to give unspaced scripts something to index when a proper segmenter is unavailable. ## Why index prefixes instead of matching them at query time A prefix query can be answered without any n-grams, by walking the sorted term dictionary from the prefix and expanding to every term that starts with it. This works, but the expansion is unbounded: a one- or two-character prefix on a large vocabulary can expand into a huge disjunction of terms, and that expansion cost is paid on every keystroke, per shard, while the user is waiting. Turning prefixes into ordinary indexed terms moves that cost from query time to index time, where it is paid once per document and amortised. A keystroke then costs exactly one dictionary lookup, and it composes cleanly with filters and with the rest of the query. ## What it costs **Index size and write throughput.** With an edge-gram range of *min* to *max*, each token of sufficient length becomes up to *max − min + 1* terms, each with its own postings entry. Index size grows several-fold on that field, the term dictionary grows, segment merges have more to do, and indexing throughput drops. On a large corpus this is the dominant objection, and it is why the field is usually a dedicated copy used only for the suggest path rather than the main searchable field. **Distorted term statistics.** A one-character gram like `s` appears in an enormous share of documents, so its IDF is near zero, while longer grams behave more normally. Worse, a single document contributes multiple grams from the same token, inflating term frequency in ways that have nothing to do with topical relevance. The practical consequence is that ranking on an edge-gram field is close to meaningless, and teams usually score on the real analyzed field and use the gram field only for matching — or disable length normalisation and frequency contributions on it. **Query-side asymmetry is mandatory.** If the user's input is n-grammed too, a query for `shoes` emits `s`, `sh`, `sho`, `shoe`, `shoes`, and the `s` clause alone matches nearly every document. Precision collapses and the result list looks random. The query must be analyzed as a plain token and matched against the indexed grams. This is the canonical example of *deliberate* index/query analyzer asymmetry. **Rigidity.** The gram range is materialised into the postings. Extending it — say from a maximum of 10 to 20 characters so longer prefixes still match — requires reindexing the corpus. Likewise, prefixes longer than the maximum simply stop matching unless the query is truncated to the maximum length, a subtle bug where autocomplete works until the user types the eleventh character. **Full n-grams are far worse.** Substring grams over a length range produce on the order of token-length times range terms per token, so index inflation is dramatic and statistics are even noisier. Reserve them for genuinely infix use cases. ## Alternatives and when to prefer them - **Term-dictionary prefix scan.** Fine when the field's vocabulary is small, prefixes are reasonably long, or query volume is low. Zero index cost, no reindex to change behaviour. - **A dedicated suggester structure.** Search engines can build a finite-state automaton over a suggestion vocabulary that returns completions in near-constant time with optional weights. It is compact and purpose-built, but it lives beside the index, needs its own build, and does not compose with arbitrary filters as naturally. - **A separate suggestion index built from query logs.** Often the best product answer: complete to *what people actually search*, weighted by popularity and freshness, rather than to every token that happens to appear in the corpus. It is smaller, more relevant, and decoupled from the document index entirely. - **Matching whole-word prefixes only on the last token.** A cheap middle ground: analyze the query normally, treat all but the final token as complete words, and only prefix-match the token the user is still typing. ## How to decide Ask three questions: how large is the field's vocabulary, how short are the prefixes you must serve, and does the suggestion need to be ranked by anything other than lexical match? Small vocabulary and long prefixes favour a dictionary scan; keystroke-latency requirements on a big corpus favour indexed edge grams on a dedicated field; a product where suggestions should reflect demand favours a query-log-driven suggestion index. And whichever you choose, budget for the reindex, because every one of these choices is materialised at index time.

  • What goes wrong if the user's query is analyzed with the same edge n-gram filter?
    The query explodes into its own prefixes, and the shortest of them matches almost every document. A search for "shoes" would emit "s", "sh", "sho", "shoe", "shoes", and the "s" clause alone drags in the whole corpus, so ranking is dominated by noise. The query must be matched as a single plain token against the indexed grams.
  • Why is ranking on an edge n-gram field usually meaningless?
    Short grams appear in a huge share of documents so their IDF is near zero, and one token contributes several grams so term frequency is inflated without any topical meaning. Teams therefore match on the gram field but score on the normally analyzed field, or strip frequency and length normalisation from the gram field entirely.
  • When would you prefer a query-log-driven suggestion index over indexing prefixes of your corpus?
    When suggestions should reflect demand rather than vocabulary. Completing to what users actually search, weighted by popularity and recency, is smaller, more relevant, and avoids suggesting rare corpus tokens nobody wants. It also decouples suggest from the document index, so you can iterate on it without reindexing content.
  • Autocomplete works until the user types the eleventh character, then results vanish. What is the likely cause?
    The indexed gram range has a maximum length shorter than the typed prefix, so no indexed term equals the query token. The fixes are to truncate the query to the maximum gram length before lookup, or to widen the range and reindex. It is a classic symptom that the range is materialised in the postings.

Edge n-grams are like filing a book under every prefix of its title: instant to find while typing, but the filing cabinet gets many times larger and needs rebuilding whenever you change how many prefixes you file.

saying these in an interview costs you the question

  • Applies the same n-gram analysis to index and query side
  • Ranks results on the n-gram field and trusts the scores
  • Thinks the gram length range can be changed without reindexing
  • Ignores the multiplication of terms and index size
  • Assumes edge n-grams are the only way to do prefix matching

context