Fuzzy Matching in Python: FuzzyWuzzy vs RapidFuzz vs Dedupe (2026 Comparison)
If you've searched for "fuzzy matching in Python" recently, you've almost certainly landed on one of three libraries: FuzzyWuzzy, RapidFuzz, or Dedupe. They come up in every Stack Overflow answer, every "clean your data with pandas" blog, and every internal wiki about record linkage. They also solve slightly different problems, and the wrong choice will cost you either performance or a lot of custom code.
This post is a hands-on comparison for engineers building fuzzy matching or deduplication pipelines in Python in 2026. Runnable code, honest benchmarks, and a straight answer to "which one should I use?"
What's in this guide
Background: What Fuzzy Matching Actually Does
Fuzzy matching is a family of algorithms that measure how similar two strings (or two records) are on a numeric scale — typically 0-100 — instead of returning a hard true/false. The core primitives are:
- Levenshtein distance: the minimum number of insertions, deletions, or substitutions to turn one string into another.
- Jaro-Winkler: a similarity metric that weights prefixes more heavily. Good for names.
- Token-based ratios: tokenize both strings, sort or set-ify, then measure similarity of the tokens. Handles word-order differences.
- Q-gram / n-gram: compare the set of overlapping character windows. Robust to insertions.
If you want the plain-English version of why these matter, we wrote it up here: Fuzzy Matching Explained — how to match "Acme Corp" to "ACME Corporation" without writing code. This post assumes you're going to write code.
FuzzyWuzzy (And Why It's on Life Support)
FuzzyWuzzy was released by SeatGeek in 2011 and became the de facto standard for a decade. If you've done fuzzy matching in Python before 2022, you probably wrote something like this:
from fuzzywuzzy import fuzz, process
fuzz.ratio("Acme Corp", "ACME Corporation")
# 70
fuzz.token_sort_ratio("Acme Corp", "ACME Corporation")
# 76
fuzz.partial_ratio("Acme Corp", "ACME Corporation")
# 100
choices = ["Acme Corporation", "3M Company", "Delta Air Lines"]
process.extractOne("acme corp", choices)
# ('Acme Corporation', 76)
It's a clean API. The problem in 2026 is:
- The original repo has been effectively unmaintained for years.
- The C-extension dependency (
python-Levenshtein) is GPL-licensed, which can be a problem for commercial redistribution. - Without
python-Levenshteininstalled, FuzzyWuzzy falls back to a pure-Python implementation that is very slow — you'll see a warning in the terminal. - The rename fork
TheFuzzis API-compatible but doesn't fix the performance ceiling.
Recommendation: don't start a new project with FuzzyWuzzy. If you have legacy code using it, RapidFuzz is a nearly drop-in replacement.
RapidFuzz (The Modern Default)
RapidFuzz is a C++ rewrite of FuzzyWuzzy's scoring functions, with a compatible Python API, SIMD-accelerated inner loops, and MIT licensing. It's the library we recommend for any new work in 2026.
Install:
pip install rapidfuzz
Basic scoring looks almost identical to FuzzyWuzzy:
from rapidfuzz import fuzz, process
fuzz.ratio("Acme Corp", "ACME Corporation")
# 69.56521739...
fuzz.token_sort_ratio("Acme Corp", "ACME Corporation")
# 76.0
fuzz.WRatio("Acme Corp", "ACME Corporation")
# 90.0 — blended scorer, good default
choices = ["Acme Corporation", "3M Company", "Delta Air Lines"]
process.extractOne("acme corp", choices, scorer=fuzz.WRatio)
# ('Acme Corporation', 90.0, 0)
The real power shows up when you want to score all pairs between two lists (a typical deduplication or record-linkage workload). Use process.cdist:
import pandas as pd
from rapidfuzz import fuzz, process
file_a = pd.read_csv("customers_from_stripe.csv")
file_b = pd.read_csv("customers_from_hubspot.csv")
scores = process.cdist(
file_a["company"].tolist(),
file_b["company"].tolist(),
scorer=fuzz.token_sort_ratio,
workers=-1, # use all CPU cores
)
# scores is a 2D numpy array of shape (len(a), len(b))
For each row in file A, find the best candidate in file B:
import numpy as np
best_idx = scores.argmax(axis=1)
best_score = scores.max(axis=1)
file_a["best_match_b"] = file_b["company"].values[best_idx]
file_a["match_score"] = best_score
confident = file_a[file_a["match_score"] >= 90]
review = file_a[(file_a["match_score"] >= 75) & (file_a["match_score"] < 90)]
unmatched = file_a[file_a["match_score"] < 75]
For a multi-column weighted match (which is usually what you actually want — see why single-column matching isn't enough), compute one score per column and blend:
weights = {"company": 0.45, "domain": 0.35, "city": 0.20}
combined = np.zeros((len(file_a), len(file_b)))
for col, w in weights.items():
combined += w * process.cdist(
file_a[col].fillna("").tolist(),
file_b[col].fillna("").tolist(),
scorer=fuzz.token_sort_ratio,
workers=-1,
)
When to use RapidFuzz: you have your matching logic figured out, you know your columns and weights, and you just need to score things fast. This covers 80% of real-world fuzzy matching in Python.
Scale note: the naive cdist above is O(N×M). Fine up to about 50,000 x 50,000. Above that, use blocking: only compare records that share a starting letter, a zip code, a phone area code, or a min-hash bucket. RapidFuzz doesn't do blocking for you — you write the block generator yourself, or you use Dedupe.io.
Dedupe.io (When You Want a Pipeline, Not a Scorer)
Dedupe is a different kind of library. RapidFuzz gives you similarity scores. Dedupe gives you a full record-linkage pipeline: active-learning training, automatic blocking with a learned predicate set, clustering, and canonicalization.
Install:
pip install dedupe
A minimal example (dedupe within one dataset):
import dedupe
fields = [
{"field": "name", "type": "String"},
{"field": "address", "type": "String"},
{"field": "city", "type": "String"},
{"field": "zip", "type": "ShortString", "has missing": True},
]
deduper = dedupe.Dedupe(fields)
deduper.prepare_training(data) # data: dict of {id: {field: value, ...}}
dedupe.console_label(deduper) # interactive: y/n on candidate pairs
deduper.train()
clustered = deduper.partition(data, threshold=0.5)
# List of (record_ids_in_cluster, confidence_scores)
The console_label step is what makes Dedupe unusual: it asks you to label 20-50 candidate pairs as "yes / no / unsure" and uses those labels to learn the matching function. You can't just tell it "use token_sort_ratio at threshold 85" — it decides the equivalent by looking at your examples.
When to use Dedupe:
- You have thousands to millions of records and want automatic blocking (Dedupe generates a learned block-key set).
- The matching rules are non-obvious and you'd rather label examples than tune weights.
- You need clustering (many-to-many groups), not just pairwise matching.
- You're building a service that has to dedupe repeatedly, and can amortize the training cost.
When NOT to use Dedupe: for a one-off script or when you already know the matching rules. Training and labeling are non-trivial ceremony for a job you could do in 20 lines of RapidFuzz.
Benchmarks
All numbers are from a 2024-era 8-core laptop (M2 Pro), Python 3.12, single process where relevant, workers=-1 where the library supports it. Rounded.
| Workload | FuzzyWuzzy | RapidFuzz | Dedupe |
|---|---|---|---|
1M pairs, ratio | ~48 s | ~1.4 s | N/A (uses different scoring) |
1M pairs, token_sort_ratio | ~92 s | ~3.2 s | N/A |
10k × 10k cdist | ~14 min (with python-Levenshtein) | ~22 s | ~40 s including block learning |
| 100k rows, full dedup with clustering | Not viable without hand-written blocking | ~3-5 min with hand-written blocking | ~4-8 min including training |
The pattern: RapidFuzz is roughly 20-40x faster than FuzzyWuzzy on the same operation. Dedupe is comparable to RapidFuzz on the raw scoring but adds real value on blocking and clustering at scale.
Comparison Table & Picking Guide
| Feature | FuzzyWuzzy / TheFuzz | RapidFuzz | Dedupe.io |
|---|---|---|---|
| Actively maintained (2026) | No / minimally | Yes | Yes |
| License | GPL (with Levenshtein) | MIT | MIT |
| Speed vs FuzzyWuzzy | 1x | 20-40x | Comparable to RapidFuzz for scoring |
| Scorers included | ratio, partial, token_sort, token_set | All FuzzyWuzzy + WRatio, JaroWinkler, DamerauLevenshtein, more | Learned; you don't pick a scorer |
| Multi-column native | No | No (write it yourself with cdist) | Yes |
| Automatic blocking | No | No | Yes (learned predicates) |
| Clustering | No | No | Yes |
| Active-learning UI | No | No | Yes (console_label) |
| Learning curve | Trivial | Trivial | Moderate |
The one-line picking guide
- Starting new? Use RapidFuzz.
- Legacy FuzzyWuzzy code? Migrate to RapidFuzz — the scorer API is compatible.
- Millions of records, clustering, non-obvious rules? Use Dedupe.
- Ad-hoc cleanup, no engineer nearby? Skip Python entirely (see below).
When to Skip Python and Use a No-Code Tool
Writing a fuzzy matching pipeline in Python is fun once. It's tedious the fifth time. If any of these apply, a UI tool will save you hours:
- The person who needs the match isn't the person writing the code.
- You're doing a one-off cleanup for a CRM migration, a tradeshow list, or a vendor master file.
- You want a business user (RevOps, finance ops, marketing ops) to iterate on weights and thresholds without you.
- The output is going straight into Excel, a CRM import, or an email tool — not into a data pipeline.
DedupFuzzy runs the same core algorithms (RapidFuzz-style scorers plus domain-specific normalization) with a UI: upload CSV/XLSX, pick columns and weights, review the score bands, export a golden record file. Free for 500 rows.
Many teams end up using both — a Python pipeline in production for scheduled dedup on their main tables, and a UI tool for ad-hoc file cleanups that don't warrant a code deploy.
FAQ
What about jellyfish, textdistance, and recordlinkage?
jellyfish is a great low-level library for individual string metrics (Jaro-Winkler, Metaphone, NYSIIS) if you don't need the process/extract helpers. textdistance has the widest set of algorithms in one package, but is generally slower than RapidFuzz for the common ones. recordlinkage is closer to Dedupe in scope — a full record-linkage toolkit — but with less active development and a steeper learning curve. In practice, most teams pick RapidFuzz for scoring and either Dedupe or hand-written blocking for scale.
How do I handle non-Latin scripts?
RapidFuzz is fully Unicode-aware, so scoring on Japanese, Arabic, Cyrillic, or CJK text works. What often breaks is your tokenization assumption: token_sort_ratio splits on whitespace, and Japanese doesn't use whitespace between words. For non-Latin scripts, consider ratio or character n-grams instead of token-based scorers.
How do I match on domain (email) properly?
Don't fuzzy match on emails as strings. Split into local@domain, exact-match the domain, and fuzzy match on local part or the associated person's name. Fuzzy-matching bob@acme.com against alice@acme.com returns a high score but they're different people.
Should I use embeddings / vector similarity instead?
For product descriptions and free-text catalogs, embeddings (sentence-transformers, OpenAI, Cohere) plus cosine similarity can outperform token-based methods on semantic similarity. For structured data — names, addresses, SKUs, vendor names — token and edit-distance methods are still faster, cheaper, and more predictable. In practice, hybrid pipelines (RapidFuzz for cheap high-confidence matches, embeddings for the borderline band) are becoming common in 2026.
Wrapping Up
The Python fuzzy matching landscape has consolidated. FuzzyWuzzy was great for its era but is no longer the right pick. RapidFuzz is the modern default for scoring — fast, MIT-licensed, actively maintained, drop-in compatible. Dedupe is the right pick when you want the whole record-linkage pipeline (blocking, clustering, active learning) instead of just a scorer.
Whichever you pick, the pattern that works is the same: normalize, block, score with weights, review the middle band, merge the top band, and log everything. The code differs; the discipline doesn't — and we walk through that discipline end-to-end in The Ultimate Guide to Data Deduplication.
Related Reading
- The Ultimate Guide to Data Deduplication (2026)
- Fuzzy Matching Explained (No Code Required)
- Fuzzy Match on Multiple Columns With Weights
- Excel Power Query Fuzzy Merge Not Working? Here's Why
- Compare Two Excel Files and Find Matching Rows
Not in the mood to write a matching pipeline this afternoon? Upload your file and let DedupFuzzy run RapidFuzz-quality scoring on multiple columns with weights — no Python required. Free for 500 rows.
Try DedupFuzzy Free