Fuzzy & Multi-match Queries

intermediate elasticsearch query-dsl fuzzy multi-match levenshtein

Real users type “elasitcsearch” instead of “elasticsearch”. And they expect us to know they meant the latter. These two queries solve the “typos and multiple fields” problems.

Fuzzy query — handling typos

In simple language — “find docs where the term is almost equal to my search term, allowing for N character edits.” That’s Levenshtein distance.

Levenshtein distance = number of single-character edits (insertions, deletions, substitutions) needed to turn one string into another. catbat is distance 1. catbats is distance 2.

GET /products/_search
{
  "query": {
    "fuzzy": {
      "title": {
        "value": "elasitcsearch",
        "fuzziness": "AUTO"
      }
    }
  }
}

fuzziness options:

  • 0 — exact match (no fuzziness, same as term).
  • 1 or 2 — explicit edit distance.
  • AUTO — smart default based on term length:
    • 0-2 chars → 0 edits (must be exact)
    • 3-5 chars → 1 edit
    • 6+ chars → 2 edits

AUTO is what we want 99% of the time. Short terms shouldn’t allow typos (too many false positives).

Fuzzy inside match

The fuzzy query operates on a single term. For multi-word fuzzy search, use match with fuzziness:

GET /products/_search
{
  "query": {
    "match": {
      "title": {
        "query": "wirless headfones",
        "fuzziness": "AUTO"
      }
    }
  }
}

This finds “wireless headphones” even with two typos. Each word gets its own fuzziness allowance.

Levenshtein distance examples
"cat" → "cat" = 0 (identical)
"cat" → "bat" = 1 (sub c→b)
"cat" → "cats" = 1 (insert s)
"cat" → "ct" = 1 (delete a)
"cat" → "dogs" = 4 (too far)

Multi-match query — search across fields

In simple language — “search this text across multiple fields at once.” It’s the realistic version of any search bar that searches title + description + tags + author all together.

GET /products/_search
{
  "query": {
    "multi_match": {
      "query": "wireless headphones",
      "fields": ["title", "description", "tags"]
    }
  }
}

Field boosting

We rarely want all fields treated equally. A match in the title should count more than a match in the description. Use ^N suffix:

{
  "multi_match": {
    "query": "wireless headphones",
    "fields": ["title^3", "description^1", "tags^2"]
  }
}

Now a match in title scores 3x more than the same match in description. This dramatically improves relevance for real search bars.

Multi-match types

The type parameter controls how scores from different fields are combined:

TypeWhat it doesWhen to use
best_fields (default)Use the score of the single best matching fieldMost search bars
most_fieldsSum scores across all matching fieldsWhen the same text in multiple fields is a stronger signal
cross_fieldsTreat fields as one big field (good for names split into first/last)People search, addresses
phraseEach field tried as a phrase matchExact phrase across fields
phrase_prefixPhrase match with prefix on last termAutocomplete
{
  "multi_match": {
    "query": "manish prajapati",
    "type": "cross_fields",
    "fields": ["first_name", "last_name"],
    "operator": "and"
  }
}

cross_fields is perfect here — “manish” matches first_name, “prajapati” matches last_name, but together they should look like a single match.

Combining with fuzziness

We can do both — multi-field AND typo-tolerant:

GET /products/_search
{
  "query": {
    "multi_match": {
      "query": "wirless headfones",
      "fields": ["title^3", "description"],
      "fuzziness": "AUTO"
    }
  }
}

This is the “kitchen sink” search bar query. Multi-field, weighted, typo-tolerant.

Performance note

Fuzzy queries are expensive — ES has to compute edit distances against many terms in the index. Two safeguards:

  • prefix_length — number of leading characters that must match exactly (default 0). Setting prefix_length: 1 or 2 massively narrows the candidate set.
  • max_expansions — caps the number of terms the query expands to (default 50).
{
  "fuzzy": {
    "title": {
      "value": "elasticsearch",
      "fuzziness": "AUTO",
      "prefix_length": 2,
      "max_expansions": 100
    }
  }
}

Quick rules

  • User-facing search bars → multi_match + field boosts + fuzziness: AUTO. Best UX.
  • People/name search → cross_fields type.
  • Autocomplete → phrase_prefix type (or a dedicated suggester).
  • Short queries (< 3 chars) — disable fuzziness, too noisy.
  • Always set prefix_length: 1 in production fuzzy queries for performance.