< Academy

The Address Matching Challenge

Research
Dr Fabio Rodriguez
Senior ML Engineer

A user types "48 Charlotte Street, London" into a search field. Somewhere in a database of ten million addresses, the right one exists. The system needs to find it accurately, instantly and at scale. It sounds straightforward but it isn't. In a recent Passion Academy session, Dr. Fabio Rodríguez walked through why address matching is a surprisingly hard problem and how a four-stage pipeline solves it without reaching for an LLM until the very last moment.

Slides

Why the Obvious Approaches All Fall Short

The natural instinct is to throw an LLM at it (feed the query in, get the best match back).The problem is that to find the closest address, the LLM would need to compare the input against every entry in the database. At ten million addresses that means ten million comparisons either crammed into a single context window, or spread across ten million API calls. The latency is unworkable and the cost is extreme.

String distance methods like Levenshtein (which measure how many edits it takes to turn one string into another) are fast but brittle. They're sensitive to word order, abbreviations and formatting. "St." versus "Street", "Apt" versus "Apartment", "near" versus "close to"...all of these trip them up. They don't understand intent, only character sequences.

Embeddings capture semantic meaning well but miss the fine-grained details that matter most in addresses: unit numbers, street numbers, postal codes. Two addresses on the same street can have nearly identical embeddings while being completely different locations. Threshold tuning is difficult and false positives are a real problem at scale.

None of these approaches works alone, the solution is to combine them carefully in sequence.

Stage One: Canonicalisation

Before any matching can happen, the input needs to be cleaned. Raw address text is inconsistent: mixed case, punctuation, abbreviations, spelling variations. Canonicalisation standardises everything into a common form.

"Flat 2, Ros stret. (LDN)" becomes "flat 2 ros stret london" (lowercased, punctuation removed, abbreviations expanded). Both the query and every entry in the database go through this process, so comparisons are always made between representations in the same format. Two database indexes are then applied:

  • A B-tree index on postcode for exact matching when a postcode is provided,
  • A GIN trigram index on the canonical text for fuzzy retrieval across the full address.

Stage Two: Trigram Retrieval

Rather than scanning every row in the database, the system uses trigrams (overlapping three-character sequences) to rapidly identify the most promising candidates.

"Street" becomes STR, TRE, REE, EET. A dictionary maps every trigram to the list of database entries that contain it. When a query comes in, the system finds all entries that share the largest number of trigrams with the input. This gives a shortlist of around 200 candidates in milliseconds, without touching the rest of the database. Trigrams are particularly well suited to address matching because they capture partial matches, tolerate typos and work across different word orderings...exactly the kinds of variation addresses produce in the real world.

Stage Three: RapidFuzz Reranking

With roughly 200 candidates retrieved, the system applies a more sophisticated similarity score using a Token-Set-Ratio approach. This,

  1. Tokenises both the query and each candidate
  2. Identifies the common tokens
  3. Reconstructs a canonical form from the remaining ones
  4. Computes a Levenshtein-style similarity between the two canonical strings.

The advantage over basic string distance is that it's order-independent. I.e. "flat 2 rose street" and "rose street flat 2" score identically. It also handles typos gracefully: "flat 4 page street" against "page stret flat 4" still scores around 96 because the edit distance between "street" and "stret" is small.

Stage Four: GPT Final Decision

The top ten candidates from stage three are passed to an LLM for final disambiguation. At this point the LLM isn't searching, it's choosing between a small, pre-filtered shortlist. The context window is entirely manageable, the cost per query is negligible and the LLM can apply genuine semantic reasoning to resolve the cases that scoring alone can't handle: ambiguous house numbers, address intent, contextual clues in the original query.

If the LLM is confident, the match is confirmed. If not, the case is flagged for manual review rather than returning a wrong answer silently.

The Broader Picture

The architecture works because each stage does only what it's good at. Canonicalisation removes noise before any comparison happens. Trigram retrieval narrows ten million records to two hundred in milliseconds. RapidFuzz reranks those candidates with order-independent, typo-tolerant scoring. And GPT resolves the ambiguous edge cases that no deterministic method could handle reliably.

This applies to any search problem at scale: don't ask a powerful model to do what a fast index can do better. Use the LLM where reasoning actually matters: which is usually at the end on a small set of genuine candidates

< back to academy
< previous
Next >