similarity vs string-similarity
Choosing the Right String Matching Algorithm for Fuzzy Search and Deduplication
similaritystring-similaritySimilar Packages:

Choosing the Right String Matching Algorithm for Fuzzy Search and Deduplication

similarity and string-similarity are both JavaScript libraries designed to calculate how alike two strings are, but they use fundamentally different algorithms suited for different problems. similarity implements the Levenshtein distance algorithm, which counts the minimum number of single-character edits (insertions, deletions, or substitutions) required to change one string into another. It is ideal for typo correction and measuring exact character-level differences. string-similarity, on the other hand, uses the Dice Coefficient based on bigrams (pairs of characters). This approach is better at matching strings that have the same characters in a similar order but might have extra noise or slight reordering, making it popular for fuzzy search and record linkage where strict edit distance is too rigid.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
similarity078-07 years agoISC
string-similarity02,531-226 years agoISC

similarity vs string-similarity: Algorithm Choice for Fuzzy Matching

When implementing search bars, deduplication logic, or typo tolerance in your application, picking the right string comparison library is critical. similarity and string-similarity look similar at a glance, but they solve different problems using distinct mathematical approaches. One counts edits; the other measures overlap. Let's dig into how they work and when to use each.

🧮 Core Algorithm: Edit Distance vs. Bigram Overlap

similarity relies on the Levenshtein Distance algorithm.

  • It calculates the minimum number of single-character edits (insertions, deletions, substitutions) needed to turn string A into string B.
  • The result is a raw integer representing "distance." A score of 0 means the strings are identical.
  • This is perfect for detecting typos where a user missed a letter or hit a wrong key.
import similarity from 'similarity';

// Comparing 'kitten' and 'sitting'
const distance = similarity('kitten', 'sitting');

console.log(distance); 
// Output: 3 
// Explanation: kitten -> sitten (1) -> sittin (2) -> sitting (3)

string-similarity uses the Dice Coefficient based on bigrams.

  • It breaks strings into pairs of characters (bigrams) and compares how many pairs the two strings share.
  • The result is a normalized float between 0 (no match) and 1 (perfect match).
  • This approach is more forgiving of transpositions (swapped letters) and extra noise.
import stringSimilarity from 'string-similarity';

// Comparing 'kitten' and 'sitting'
const score = stringSimilarity.compareTwoStrings('kitten', 'sitting');

console.log(score); 
// Output: ~0.57
// Explanation: Measures the ratio of shared character pairs, not edit steps.

📏 Output Format: Raw Count vs. Normalized Score

The way these libraries return data dictates how you write your conditional logic.

similarity returns a raw count.

  • Lower numbers mean better matches.
  • You must define your own threshold based on string length. A distance of 2 is huge for a 4-letter word but negligible for a 100-letter paragraph.
  • You often need to normalize this manually if you want a percentage.
import similarity from 'similarity';

const str1 = 'apple';
const str2 = 'aple'; // Missing 'p'

const distance = similarity(str1, str2);
const isClose = distance <= 1; // Allow 1 typo

console.log(`Distance: ${distance}, Is Close: ${isClose}`);
// Output: Distance: 1, Is Close: true

string-similarity returns a normalized score.

  • Higher numbers mean better matches (1.0 is perfect).
  • This makes setting global thresholds easier (e.g., "accept any result above 0.8").
  • It handles length differences more gracefully without manual normalization.
import stringSimilarity from 'string-similarity';

const str1 = 'apple';
const str2 = 'aple'; 

const score = stringSimilarity.compareTwoStrings(str1, str2);
const isClose = score >= 0.8; // Accept 80% similarity

console.log(`Score: ${score}, Is Close: ${isClose}`);
// Output: Score: ~0.89, Is Close: true

🔍 Finding Best Matches: Manual Loops vs. Built-in Helpers

A common task is finding the best match for a user's query from a list of options.

similarity requires you to write the looping logic yourself.

  • The library only compares two strings at a time.
  • You must map over your array, calculate distances, and sort the results manually.
  • This gives you full control but adds boilerplate code.
import similarity from 'similarity';

const query = 'ninja';
const candidates = ['turtle', 'ninjas', 'ping', 'ninja-turtle'];

const bestMatch = candidates.reduce((best, current) => {
  const currentDist = similarity(query, current);
  const bestDist = similarity(query, best);
  // Lower distance is better
  return currentDist < bestDist ? current : best;
});

console.log(bestMatch); 
// Output: 'ninjas' (Distance 1)

string-similarity provides a dedicated helper for this exact scenario.

  • It includes findBestMatch, which returns a ranked list of results with scores.
  • This saves development time and reduces the chance of sorting errors.
  • The output includes both the best match and a sorted array of all ratings.
import stringSimilarity from 'string-similarity';

const query = 'ninja';
const candidates = ['turtle', 'ninjas', 'ping', 'ninja-turtle'];

const match = stringSimilarity.findBestMatch(query, candidates);

console.log(match.bestMatch.target); 
// Output: 'ninjas'

console.log(match.ratings);
// Output: Sorted array of all candidates with their scores

⚡ Performance and Edge Cases

Performance characteristics differ based on string length and content.

similarity can become slow on very long strings.

  • Levenshtein distance has a time complexity related to the product of the lengths of both strings (O(n*m)).
  • Comparing two 1,000-character paragraphs can be computationally expensive.
  • It is strictly character-order dependent. Swapping two words results in a high distance score.
import similarity from 'similarity';

// Swapping words creates a large distance
const s1 = 'the quick brown fox';
const s2 = 'the brown quick fox';

console.log(similarity(s1, s2)); 
// Output: High number (many edits needed to swap words)

string-similarity generally scales better for fuzzy matching tasks.

  • While still O(n*m) in worst case, the bigram comparison is often faster in practice for search-like queries.
  • It handles word swaps better because the character pairs still exist, just in a different order.
  • It may produce false positives on very short strings (e.g., 'it' and 'ti' might score highly).
import stringSimilarity from 'string-similarity';

// Swapping words retains a decent score
const s1 = 'the quick brown fox';
const s2 = 'the brown quick fox';

console.log(stringSimilarity.compareTwoStrings(s1, s2)); 
// Output: Moderate/High score (many bigrams still match)

🛠️ Real-World Implementation Patterns

Pattern 1: Spell Correction (Typo Tolerance)

If you are building a search bar that needs to fix small typos like "iphon" to "iphone":

  • Use similarity if you want strict control over how many keystrokes are allowed.
  • Use string-similarity if you want a quick implementation with a confidence threshold.
// Using string-similarity for quick spell check
import stringSimilarity from 'string-similarity';

function correctSpell(input, dictionary) {
  const match = stringSimilarity.findBestMatch(input, dictionary);
  // Only correct if confidence is high
  return match.bestMatch.rating > 0.7 ? match.bestMatch.target : input;
}

Pattern 2: Data Deduplication

If you are merging user lists and need to find duplicates like "John Smith" vs "Jon Smith":

  • Use string-similarity. The Dice coefficient handles the transposition of 'h' and missing 'h' gracefully, providing a clear percentage match to set a cutoff.
// Using string-similarity for deduplication
import stringSimilarity from 'string-similarity';

const isNewEntryUnique = (newName, existingList) => {
  return !existingList.some(existing => {
    return stringSimilarity.compareTwoStrings(newName, existing) > 0.85;
  });
};

📊 Summary Comparison

Featuresimilaritystring-similarity
AlgorithmLevenshtein DistanceDice Coefficient (Bigrams)
OutputInteger (Edit Count)Float (0.0 to 1.0 Score)
Best Match LogicManual Implementation RequiredBuilt-in findBestMatch
Ideal ForSpell checkers, diff tools, strict validationFuzzy search, record linkage, autocomplete
SensitivitySensitive to character orderTolerant of reordering and noise
InterpretationLower is betterHigher is better

💡 Final Recommendation

Choose similarity if your use case revolves around editing. If you need to tell a user "you are 2 characters away from a valid password" or build a strict diff tool, the Levenshtein distance is the industry standard. It provides precise, actionable data about character differences.

Choose string-similarity if your use case revolves around searching. If you are building a fuzzy search bar, matching customer names across databases, or suggesting tags, the Dice Coefficient offers a more intuitive "confidence score." The built-in findBestMatch helper also significantly reduces boilerplate code, making it the pragmatic choice for most frontend search features.

How to Choose: similarity vs string-similarity

  • similarity:

    Choose similarity when you need to measure the exact 'cost' of transforming one string into another, such as in spell-checkers or password strength analyzers. It is the right tool when the number of specific character edits matters more than general visual resemblance. Since it returns a raw distance count (lower is better), it fits well in scenarios where you define a maximum allowable edit threshold. Avoid this if you need a normalized 0-to-1 score out of the box or if your data contains significant rearranging of words.

  • string-similarity:

    Choose string-similarity when building fuzzy search features, autocomplete suggestions, or deduplication tools where users might type phrases with extra spaces or slight word swaps. Its Dice Coefficient algorithm returns a normalized score between 0 and 1, making it easier to set confidence thresholds without complex math. It handles longer strings and minor reordering better than Levenshtein distance. Do not use this if you require strict character-perfect validation or need to know the exact number of edits required to fix a string.

README for similarity

similarity

Build Coverage Downloads Size

How similar are these two strings?

Install

npm:

npm install similarity

Use

var similarity = require('similarity')

similarity('food', 'food') // 1
similarity('food', 'fool') // 0.75
similarity('ding', 'plow') // 0
similarity('chicken', 'chick') // 0.714285714
similarity('ES6-Shim', 'es6 shim') // 0.875 (case insensitive)
similarity('ES6-Shim', 'es6 shim', {sensitive: true}) // 0.5 (case sensitive)

API

similarity(left, right[, options])

Get the similarity (number) between two values (strings), where 0 is dissimilar, and 1 is equal.

  • options.sensitive (boolean, default: false) — Turn on (true) to treat casing differences as differences

CLI

Usage: similarity [options] <word> <word>

How similar are these two strings?

Options:

  -h, --help           output usage information
  -v, --version        output version number
  -s, --sensitive      be sensitive to casing differences

Usage:

# output similarity
$ similarity sitting kitten
0.5714285714285714
$ similarity saturday sunday
0.625

See also

Note: This module uses Levenshtein distance to measure similarity, but there are many other algorithms for string comparison. Here are a few:

  • clj-fuzzy — Handy collection of algorithms dealing with fuzzy strings and phonetics
  • natural — General natural language facilities for node
  • string-similarity — Finds degree of similarity between two strings, based on Dice’s coefficient
  • dice-coefficient — Sørensen–Dice coefficient
  • jaro-winkler — The Jaro-Winkler distance metric

License

ISC © Zeke Sikelianos