Skip to content
Craft Solution Tech

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

Infographic, in French: the Jaccard formula shown as a Venn diagram, the film-taste example landing on 0.33, the application domains, and the two limits of the metric.
The formula, the example, the use cases and the limits — on a single screen. (Original graphic, in French.)

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?

DomainUse
RecommendationFind users with similar tastes
NLP / searchCompare documents through their keywords
Duplicate detectionSpot near-identical texts
Fuzzy matchingCompare strings despite typos
ClusteringGroup 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

Tell us what you are trying to build.

Message us on WhatsApp
Back to the blog