Your CRM holds two records for the same company and the matcher cannot see it. It also holds two records for genuinely different companies that the matcher swears are identical. Both failures trace back to the same root cause, and it is not your data.
Fuzzy string matching for company names is usually treated as a library call: import a scorer, pick a number, ship it. That approach fails in a specific and measurable way. We scored 16 company-name pairs with RapidFuzz 3.14.5 and jellyfish 1.2.1, verifying each entity against the Global Legal Entity Identifier Foundation register. Nine of those pairs are legally distinct companies. After normalisation, all nine scored a perfect 100 on token_set_ratio.
In Validity’s 2025 survey of 602 CRM users and stakeholders, 76% said less than half of their organisation’s CRM data is accurate and complete. Name matching is where a large share of that inaccuracy is manufactured, one confident wrong merge at a time.
Direct answer — How do you do fuzzy string matching on company names?
Fuzzy string matching for company names is a two-stage process. First normalise both names with a fixed, ordered rule set: fold diacritics, expand ampersands, strip punctuation, then remove trailing legal forms such as Ltd or GmbH. Then score the normalised pair with a string similarity metric. Token metrics handle word-order and abbreviation differences that edit distance cannot. Set the accept threshold by scoring a labelled sample of your own pairs, never by copying a published number.
Key Takeaways
- The standard algorithms were designed for surnames and error-correcting codes. Company names break their core assumptions.
- Normalisation must run before scoring, and the rule order matters: strip punctuation before legal forms, or
Ltd.never matchesLtd. - Normalisation raises every score, including the wrong ones. It buys recall and costs precision.
token_set_ratioreturned 100 for all nine legally distinct pairs we tested. It cannot separate a parent from its own subsidiary.- No threshold rescues identical strings: “Bank of China Limited” is three separate registered entities.
- Derive your bands from a labelled sample of your own records. Budget about two hours for a first pass.
Why fuzzy string matching breaks on company names
Fuzzy string matching breaks on company names because the standard algorithms were built for surnames and typographical errors, not multi-token legal entities. This is not a criticism of the algorithms. It is a mismatch between what they were designed to measure and what a CRM asks them to decide.
Where the standard algorithms came from
Soundex was patented in 1918. Robert C. Russell’s patent, titled simply “Index”, describes grouping names “phonetically rather than according to the alphabetical construction of the names” so that variant spellings of a person’s name sit together in a card index. Jaro-Winkler came later and carries the same DNA: Winkler’s 1990 paper for the Fellegi-Sunter model tuned its prefix weighting on “typographical variation in strings such as first name or surname”, for the Post Enumeration Survey of the 1990 US census. Levenshtein distance, published in 1965, was not about names at all; it described error-correcting codes for binary sequences.
So the three metrics every tutorial reaches for were calibrated on human names and transmission errors. A company name is a different object. It is several tokens long, it carries a jurisdiction-specific legal form, and the tokens that distinguish two real entities are frequently the most ordinary words on the page.
The four ways company names differ
Company-name variation clusters into four kinds, and each one defeats a different metric. Confusing them is why single-metric matching plateaus. Getting the score right is only half the job; something downstream still has to decide which account a record belongs to, and which account a lead actually belongs to is a separate decision with its own tie-breakers.

| Failure mode | Example pair | What raw edit distance does | What actually fixes it |
|---|---|---|---|
| Legal form differs | Nestlé S.A. / Nestle SA | Penalises the suffix and the accent as real differences | Normalisation: fold diacritics, strip the legal form |
| Token order differs | Deutsche Bank AG / Bank, Deutsche (AG) | Scores 62, treats reordering as many edits | A token metric that sorts before comparing |
| Acronym or alias | IBM / International Business Machines Corporation | Scores 18, no shared substring to find | An alias table. No metric solves this |
| One added division token | Siemens AG / Siemens Energy AG | Scores high, because one word is a small edit | A guardrail, not a threshold |
That last row is the expensive one. The difference between a parent and a separately incorporated division is often a single word, which is exactly where edit distance is smallest and the commercial consequence is largest. Name is also simply a poor identifier compared with the fields that identify a company more reliably than its name, which is why mature matchers treat the name as one signal rather than the deciding one.
Normalise company names before you score them
To normalise a company name, apply a fixed, ordered set of rules before any similarity score is calculated. Order is not a stylistic choice here. Strip legal forms before punctuation and Ltd. keeps its full stop, so it never matches the token Ltd and the rule silently does nothing.
The nine-rule normalisation pipeline
This is the pipeline we used for every measurement in this article. We publish it as CNN-1.0 so it can be cited and reproduced rather than paraphrased.
| # | Rule | Input → Output | Why it sits here |
|---|---|---|---|
| 1 | Fold Unicode and diacritics | Coöperatieve → Cooperatieve | Before case folding, so mapped characters fold consistently |
| 2 | Case fold | BANK OF CHINA → bank of china | Every later comparison is case-insensitive |
| 3 | Expand conjunctions | Procter & Gamble → procter and gamble | Before punctuation stripping, or the & is destroyed |
| 4 | Collapse dotted initialisms | S.A. → sa | Before the general strip, or S.A. becomes two tokens |
| 5 | Strip remaining punctuation | Bank, Deutsche (AG) → bank deutsche ag | Before rule 7, or Ltd. never matches Ltd |
| 6 | Collapse whitespace | apex tool → apex tool | Tokenisation downstream assumes single spaces |
| 7 | Drop a leading article | The Procter and Gamble Company → procter and gamble company | Before legal-form stripping, so the tail is reached cleanly |
| 8 | Strip trailing legal forms, iteratively | procter and gamble company → procter and gamble | Tail only, never mid-string, and never if it empties the name |
| 9 | Protect descriptor tokens | Liberty Global Holdings keeps holdings | A guard on rule 8. Without it, rule 8 manufactures false positives |
Rule 9 is the one most implementations omit, and it is the one that prevents the pipeline from doing harm. Holdings, Group, Partners, Ventures, Labs, International, Technologies and Solutions all look like noise and none of them are: they routinely mark a distinct registered entity. Rule 9 also protects purely numeric and single-letter tokens.
Legal forms and entity tokens by jurisdiction
Rule 8 needs a token list, and most articles hand-wave it as “strip LLC, Inc, Corp, Ltd”. That covers one country. The authoritative source is ISO 20275, published by GLEIF as the Entity Legal Forms Code List, version 1.6 of February 2026, which catalogues more than 3,600 legal forms across more than 200 jurisdictions and is released under CC0.
GLEIF tells you which forms exist. It does not tell you whether stripping one is safe. That judgment is the column below.
| Jurisdiction | Common legal-form tokens | CRM action |
|---|---|---|
| United States | inc, incorporated, corp, corporation, co, company, llc, lp, llp, pllc, pc | Strip |
| United Kingdom, Ireland | ltd, limited, plc, llp, cic, dac, teoranta | Strip |
| Canada, Australia, New Zealand | ulc, cie, pty ltd, pty limited, nl | Strip |
| Germany, Austria, Switzerland | gmbh, ag, kg, kgaa, ohg, se, ug, gesellschaft mit beschränkter haftung | Strip |
| Netherlands, Belgium | bv, nv, cv, vof, ua | Strip |
| France, Spain, Italy, Portugal, Brazil | sarl, sas, sa, snc, sl, slu, spa, srl, ltda, lda | Strip |
| Nordics | ab, hb, kb, as, asa, aps, oy, oyj, ky | Strip |
| Japan, Singapore, India, Poland | kk, kabushiki kaisha, pte ltd, pvt ltd, sp z o o | Strip |
| Any jurisdiction | holdings, group, partners, ventures, labs, international, technologies, solutions, energy, capital, media | Keep, and flag as a review signal |
IMPORTANT
Several tokens are legal forms in one language and ordinary words in another. as is a Norwegian legal form and an English preposition; co ends both a legal form and Coca-Cola’s registered name. Strip legal forms from the tail of the string only, never mid-string.
Name standardisation is the first step of any wider hygiene programme, and it belongs alongside the deduplication and validation work that a CRM data cleansing programme sequences around it. It is also worth checking whether you need to derive the canonical name at all, because an enrichment provider can often hand you the registered legal name directly, which changes normalisation from a guess into a lookup.
Where normalisation goes too far
Aggressive normalisation is the most common way teams make matching worse while believing they improved it. Treat “Global”, “Media” or “Holdings” as noise and Liberty Global Ltd. collapses onto Liberty Global Holdings Ltd. Those are two distinct registered entities with different LEIs in different jurisdictions. The pipeline did not find a match; it created one.
Which similarity metric to use on company names
String similarity metrics score how close two names are, and each one is blind to a different kind of company-name difference. Choosing between them is the actual engineering decision, and it is not the same as choosing a number.
Edit distance, and a correction worth knowing
Edit distance counts the single-character operations needed to turn one string into another. It handles typos and truncation well and token reordering badly. One detail trips up most write-ups: fuzz.ratio is not Levenshtein. The RapidFuzz documentation defines it as “the normalized Indel similarity”, which permits insertions and deletions but not substitutions. Levenshtein allows substitution, so the two disagree on exactly the single-character swaps that separate similar company names.
Token metrics
Token metrics split the name into words and compare the sets, which is why they beat edit distance on reordering. Deutsche Bank AG against “Bank, Deutsche (AG)” scores 62 on fuzz.ratio and 100 on token_sort_ratio. That gain is real. The cost arrives with token_set_ratio, which the same documentation describes as comparing “the words in the strings based on unique and common words between them”. Because it discounts the words that are not shared, an added division token becomes nearly free, and the metric reports a perfect match between a parent and its own subsidiary.
Phonetic and prefix metrics
Soundex and Metaphone reduce a string to a phonetic key, which is useful for blocking and unreliable for scoring multi-token company names in mixed languages. Jaro-Winkler weights early characters heavily, which flatters any pair sharing a first word. Every company in a corporate family shares its first word.
| Metric | What it is blind to | Use it when | Avoid it when |
|---|---|---|---|
fuzz.ratio (Indel) | Word order; abbreviations | Names differ by typos or truncation | Fields arrive in inconsistent word order |
token_sort_ratio | Extra or missing tokens | Word order is unreliable, token count is not | One name carries a division or region word |
token_set_ratio | Whole added tokens | Recall matters more than precision, with review | You are auto-merging. It cannot separate parent from subsidiary |
| Jaro-Winkler | Differences after the prefix | Ranking candidates inside a block | Names share a first word, as corporate families do |
| Jaccard on tokens | Spelling entirely | A cheap first filter | Names are one or two tokens long |
The decision framework is short. Use token_sort_ratio as the default company-name scorer. Use fuzz.ratio as a second opinion when token counts match. Use Jaro-Winkler to rank candidates you have already blocked, never as the accept signal. Avoid token_set_ratio for any automatic merge. Reach for an alias table, not a metric, when acronyms and rebrands appear.
PRO TIP
Check the licence before the benchmark. RapidFuzz is MIT (3.14.5, April 2026) and thefuzz is MIT (0.22.1, January 2024) and runs on RapidFuzz underneath. The original fuzzywuzzy is GPLv2 and was last released in February 2020, which matters if you are embedding it in commercial code.
Worth saying plainly: a name that should never have reached the matcher is not a matching problem. Free-text junk, test records and personal email domains are cheaper to reject at capture, which is what a validation gate ahead of the matcher is for.
Cut the comparison count with blocking
Blocking groups records into candidate buckets so the matcher compares thousands of pairs instead of billions. Without it, comparison count grows quadratically and any real CRM becomes uncomputable.
Comparisons = n × (n − 1) ÷ 2Splink, the record-linkage library published by UK Ministry of Justice Analytical Services, puts the consequence in plain numbers: a dataset of one million records generates around 500 billion pairwise comparisons, and the overwhelming majority cannot possibly be matches.

Blocking keys that work for company names
Good keys are cheap, stable and available on both sides. The first token of the normalised name works well because normalisation has already removed the articles and punctuation that would otherwise scatter it. Email domain, country and the first four characters of the normalised name are all serviceable. Phonetic keys are reasonable here, where a false grouping costs compute rather than correctness.
Blocking is where recall dies
A true pair split across two blocks is never compared, so no threshold can recover it. Block on country and every cross-border duplicate survives forever. This is the failure mode worth testing deliberately: take pairs you know are matches, run the blocking step alone, and count how many end up in different buckets. Blocking also sets the latency budget, which matters because inbound records are usually on a clock that the routing layer’s speed-to-lead targets already define.
How to set and defend a match threshold
To set a match threshold, score a labelled sample of your own pairs and read precision and recall at each cutoff. Copying a number from an article, including this one, produces a threshold you cannot defend when someone asks why a deal landed on the wrong rep.
Label a stratified sample
Take roughly 300 candidate pairs, drawn deliberately across the score range rather than randomly, since a random draw from a real CRM is almost all obvious non-matches. Mark each pair same entity or different entity using a registry rather than intuition. The GLEIF LEI register is free, and two distinct LEIs settle the question.
Read the trade-off, then choose
Precision is the share of accepted pairs that are genuinely the same entity. Recall is the share of true pairs you accepted. Raising the cutoff buys precision and loses recall, and there is no setting that gives you both. What the curve tells you is where the cost changes character.

Pick the cutoff by asking which error you can afford. A wrong merge destroys history and is expensive to unwind; a missed match leaves a duplicate that someone will notice later. Most teams discover their auto-merge bar sits higher than they assumed. The same discipline applies to any scoring cutoff you have to justify, which is the argument behind choosing a cutoff you can defend to a sales leader.
Seed bands you are meant to replace
If you need somewhere to start before the sample is labelled, begin at 92 for auto-accept on token_sort_ratio over CNN-1.0 names, 80 to 92 for the review queue and below 80 for reject. Treat those as placeholders with no evidential weight. They are a starting point for calibration, not a result.
Workflow · 2 hours
How to calibrate a company-name matcher: one first pass
Build and calibrate a company-name matcher against your own records, ending with auto-accept, review and reject bands you can defend.
Normalise both name lists
Run every name through the nine CNN-1.0 rules in order. Store the normalised key in its own column so you can compare raw against normalised later.
Generate candidate pairs with a blocking key
Block on the first token of the normalised name plus country. Count how many pairs survive; if it is still millions, add a second key.
Score every candidate with two metrics
Compute
token_sort_ratioandfuzz.ratiofor each pair. Keep both columns; disagreement between them is a useful review signal.Hand-label 300 pairs across the score range
Sample deliberately from high, middle and low scores. Resolve each pair against a registry, not intuition. This step takes most of the two hours.
Read precision and recall at each cutoff
Step the threshold in increments of two and tabulate both measures. Find the point where precision starts falling faster than recall rises.
Set the auto-accept, review and reject bands
Write the three numbers down with the date and the sample size beside them, then re-run the pass whenever a new data source is connected.
Budget about two hours for a first calibration pass, most of it hand-labelling. That estimate covers one pass over one pair of lists, not a production deployment with monitoring and re-training.
False positives: high scores, different companies
A false positive is a pair that scores above your threshold but belongs to two different legal entities. This is the part of company-name matching that thin write-ups raise and then abandon, so here is the measured version.
Every score below comes from RapidFuzz 3.14.5 and jellyfish 1.2.1 over CNN-1.0-normalised names. Every “different entity” pair was confirmed as two separately registered entities in the GLEIF LEI register. One row, marked as such, is a synthetic export artefact rather than an observed record.
| Pair | Same legal entity? | fuzz.ratio | token_set_ratio raw → CNN-1.0 | Jaro-Winkler |
|---|---|---|---|---|
| Siemens Aktiengesellschaft / Siemens Energy AG | No | 66.7 | 58.3 → 100.0 | 90.0 |
| Siemens Aktiengesellschaft / Siemens Aktiengesellschaft Österreich | No | 31.8 | 100.0 → 100.0 | 83.8 |
| Allianz SE / Allianz Technology SE | No | 56.0 | 100.0 → 100.0 | 87.8 |
| Liberty Global Ltd. / Liberty Global Holdings Ltd. | No | 75.7 | 100.0 → 100.0 | 92.2 |
| Coöperatieve Rabobank U.A. / Coöperatieve Rabobank Bollenstreek U.A. | No | 76.4 | 100.0 → 100.0 | 92.4 |
| Nestlé S.A. / NESTLE ECUADOR S.A. | No | 60.0 | 53.3 → 100.0 | 88.6 |
| Apex Tool Group, LLC / APEX TOOL GROUP PTY. LIMITED | No | 100.0 | 29.2 → 100.0 | 100.0 |
| The Procter & Gamble Company / The Procter & Gamble Global Finance Company, LLC | No | 70.6 | 83.3 → 100.0 | 90.9 |
| Bank of China Limited / BANK OF CHINA LIMITED | No | 100.0 | 28.6 → 100.0 | 100.0 |
| Nestlé S.A. / Nestle SA | Yes | 100.0 | 80.0 → 100.0 | 100.0 |
| L’Oréal S.A. / LOreal SA | Yes | 100.0 | 76.2 → 100.0 | 100.0 |
| Procter & Gamble Co / Procter and Gamble Company | Yes | 100.0 | 84.8 → 100.0 | 100.0 |
| Coöperatieve Rabobank U.A. / Cooperatieve Rabobank UA | Yes | 100.0 | 92.0 → 100.0 | 100.0 |
| Deutsche Bank AG / Bank, Deutsche (AG) (synthetic) | Yes | 61.5 | 91.4 → 100.0 | 64.7 |
| Meta Platforms, Inc. / Facebook, Inc. | Yes | 18.2 | 47.1 → 18.2 | 41.9 |
| IBM / International Business Machines Corporation | Yes | 17.6 | 8.7 → 11.8 | 57.7 |
High score, different company
Read the token_set_ratio column again. All nine legally distinct pairs reach 100.0 after normalisation. Fourteen of the sixteen pairs reach 100.0, and only five of those fourteen are actually the same entity. On this set, accepting everything that scores 100 on token_set_ratio would have been correct 5 times out of 14.
Two rows are unwinnable by any string method. Apex Tool Group’s US and Australian entities normalise to the identical string, and “Bank of China Limited” resolves to three separately registered entities with byte-identical legal names in Australia, France and Panama. When two different companies share a name exactly, no threshold and no metric can help. Only a second field can.
IMPORTANT
Normalisation is not a precision tool. It raised token_set_ratio by 71.4 points on the Bank of China pair and by 46.7 on the Nestlé pair, both wrong matches. Normalisation buys recall and spends precision. Adding it obliges you to re-calibrate the threshold, not to relax it.

Low score, same company
The mirror case gets almost no coverage and costs just as much. IBM against its own registered name scores 17.6, and no amount of tuning fixes it. Meta Platforms scores 18.2 against Facebook, and normalisation makes it worse, because “Inc.” was the only token the two names shared. SEC filings record the former name as “Facebook Inc” until 27 October 2021, which is the fix: acronyms and rebrands need an alias table, maintained by hand, checked before scoring.
The guardrails that catch both
Guardrails are token-level rules that run after scoring and override it. Four earn their keep: reject when numeric tokens disagree, reject when geographic tokens disagree, reject when two candidates carry distinct LEIs, and demote to review when the only difference is a protected descriptor such as Holdings or Energy. Guardrails work where thresholds cannot, because they inspect which token differs rather than how much. What happens to a confirmed duplicate afterwards, including which record survives, is governed by the merge rules that decide which record wins. Subsidiary and post-acquisition ownership policy is a separate decision from matching, and it belongs with your account hierarchy rules.
One honest limit on the numbers above: 16 pairs chosen to be adversarial is a demonstration, not a benchmark. The pairs are published so the result can be reproduced, and the precision figure describes this set only.
Frequently Asked Questions
No single algorithm wins. Use token_sort_ratio over normalised names as the default, because it handles word-order differences that edit distance cannot. Add fuzz.ratio as a second opinion, and keep an alias table for acronyms. Avoid token_set_ratio for automatic merges.
Normalise both names first, then score them with RapidFuzz, which is MIT-licensed and fast. Use jellyfish for Jaro-Winkler and phonetic keys, and Splink for large-scale probabilistic linkage. Avoid the original fuzzywuzzy: it is GPLv2 and was last released in 2020.
Yes, several browser-based matchers exist, and they are fine for testing a handful of names. Do not paste customer records into one. Pasting CRM data into a third-party web form is a disclosure, and it is unlikely to be covered by your processing agreements.
There is no portable number, because the right cutoff depends on your name distribution and on which error costs you more. Start near 92 for auto-accept on normalised names, send 80 to 92 to review, then replace both figures with values measured on 300 labelled pairs of your own.






