Jaccard similarity: the simplest similarity metric in AI
One division between the intersection and the union of two sets. TypeScript implementation, fuzzy matching with n-grams, and the cases where it falls short.
4 min read

You want to compare two sets and know how alike they are? Jaccard similarity does it with a single division.
The formula
It looks scary at first 😅:
J(A, B) = |A ∩ B| / |A ∪ B|
In plain words:
Number of elements in common ÷ Total number of unique elements
The result is always between 0 (nothing in common) and 1 (identical). The closer to 1, the more similarity there is.
A concrete example
Imagine two users and the films they liked (a very simple example):
const userA = new Set(["Inception", "Matrix", "Interstellar", "Dune"]);
const userB = new Set(["Matrix", "Dune", "Blade Runner", "Arrival"]);
// Intersection (in common): Matrix, Dune → 2 elements
// Union (all unique films): 6 elements
const jaccard = 2 / 6; // = 0.33
These two users have 33% similarity in their film tastes — not exactly huge.
TypeScript implementation
function jaccardSimilarity<T>(setA: Set<T>, setB: Set<T>): number {
const intersection = new Set([...setA].filter(x => setB.has(x)));
const union = new Set([...setA, ...setB]);
if (union.size === 0) return 1; // two empty sets = identical
return intersection.size / union.size;
}
// Usage
const a = new Set(["cat", "dog", "bird"]);
const b = new Set(["cat", "fish", "bird"]);
console.log(jaccardSimilarity(a, b)); // 0.5
Where is it used in AI?
| Domain | Use |
|---|---|
| Recommendation | Find users with similar tastes |
| NLP / search | Compare documents through their keywords |
| Duplicate detection | Spot near-identical texts |
| Fuzzy matching | Compare strings despite typos |
| Clustering | Group similar items together |
In practice: fuzzy matching with n-grams
The power of Jaccard does not come from the formula, but from how you build your sets before applying it.
Example: comparing two texts despite a typo.
// Split a string into bigrams (groups of 2 characters)
function toBigrams(str: string): Set<string> {
const bigrams = new Set<string>();
const normalized = str.toLowerCase();
for (let i = 0; i < normalized.length - 1; i++) {
bigrams.add(normalized.slice(i, i + 2));
}
return bigrams;
}
const textA = "hello";
const textB = "helo"; // the second 'l' is missing
const bigramsA = toBigrams(textA); // {"he", "el", "ll", "lo"}
const bigramsB = toBigrams(textB); // {"he", "el", "lo"}
console.log(jaccardSimilarity(bigramsA, bigramsB)); // 0.75
Result: 75% similarity despite the typo. That is how a lot of plagiarism detection and approximate search tools work.
The limits
Jaccard is simple and effective, but it is not right for everything:
- No notion of frequency: a word appearing once or a hundred times counts the same
- Not suited to continuous vectors: for embeddings, cosine similarity is the one to use
- Sensitive to small sets: 2 elements in common out of 3 (66%) versus out of 100 (2%).
I am currently training in AI and documenting my progress here. If you have questions or feedback, go ahead.
Covered here
- AI
- machine learning
- TypeScript