fuzzy-tools

July 7, 2026 · View on GitHub

  1. What is it?
  2. Quick Start
  3. Installation
  4. Import
  5. What is new?
  6. Match function
  7. Filter function
  8. Benchmark results
  9. Tests

What is it?

It is the fastest functions for fuzzy matching and items filtering.

fuzzy-tools demo

https://codesandbox.io/s/fuzzytoolsexample-4r2ej

Quick Start

import { match, filter } from 'fuzzy-tools';

// Basic fuzzy match
match('fzz', 'fuzzy search');
// { score: 1 }

// Filter a list of strings
filter('fzz', ['fuzzy', 'funny', 'fazzy']);
// ['fuzzy', 'fazzy']

Installation

npm install --save fuzzy-tools

Import

ESM (recommended):

import { match, filter, matchString, matchList } from 'fuzzy-tools';

CommonJS:

const { match, filter, matchString, matchList } = require('fuzzy-tools');

Default import:

import fuzzyTools from 'fuzzy-tools';
fuzzyTools.match('fzz', 'fuzzy');

What is new?

v3.0.0

  • caseInsensitive removed (use caseSensitive: true instead of caseInsensitive: false)
  • mask not only String, but Array.of(String or RegExp). In case when it is array then search will be work as fuzzy but not by chars, but by words. ['fuz', 'sea'] (or ['fuz', /sea/]) will not be found in fuuuuzzzzzy seeeeearch, but will be found in fuzzzzzy search.

Match function

match(mask, where, options):Object | null - returns result of string matching. Returns an object if mask matches where (e.g. 'fzz' in 'fuzzy'), null if it does not (e.g. 'fzza' in 'fuzzy').

Tests cases for match

Tests cases for matchList

Tests cases for matchString

Arguments

argstypedefaultnote
maskString or Array.of(String or RegExp)noprovide what you want to find ('fzz' or ['fuz', 'sea'])
whereString or Array.of(String)nodestination string or array of strings
optionsObject{...}additional options (see below)
  • mask

    • String: 'fzz' will be found in 'fuzzy search'

    • Array.of(String or RegExp):

      ['fzz'] will NOT be found in 'fuzzy search'.

      [/fzz/] will NOT be found in 'fuzzy search'.

      [/f/, /./, /zz/] will be found in 'fuzzy search'.

      ['fuz', 'sea'] will be found in 'fuzzy search'.

      ['fuz', 'seaa'] will NOT be found in 'fuzzy search', because ALL words should be in where.

        match('fzz', 'fuzzy search'); // { score: 1 }
        match(['fzz'], 'fuzzy search'); // null
        match(['f', 'u', 'zz'], 'fuzzy search'); // { score: 1 }
        match(['f', /./, 'zz'], 'fuzzy search'); // { score: 1 }
        match(['f', /[a-z]/, 'zz'], 'fuzzy search'); // { score: 1 }
        match([/[a-x]{2}/, 'zz'], 'fuzzy search'); // { score: 1 }
        match(['fuz', 'rch'], 'fuzzy search'); // { score: 1 }
        match(['fuz', 'rch1111'], 'fuzzy search'); // null
      
  • where

    • String: result contains score is score of matching.
        match('fuz', 'fuzzy'); // { score: 1 }
      
    • Array of strings or Object({ key: String, key2: String }): result contains score is min of scores of each string (score = min(match(mask, item[0]), match(mask, item[1]), ...)).
        match('fuz', ['fuzzy', 'it is fuzzy']);
        // { score: 1, matches: { 0: { score: 1 }, 1: { score: 1 } } }
        match('fuz', { v1: 'fuzzy', v2: 'it is fuzzy' });
        // { score: 1, matches: { v1: { score: 1 }, v2: { score: 1 } } }
      
    • Array of Object({ value: String, rate: Number(>0 and <=1) }): result contains score is min of score of each value * rate (score = min(match(mask, item[0].value) / item[0].rate, ...))
        match('fuz',
          [
            { value: 'fuzzy', rate: 0.75 },
            { value: 'it is fuzzy', rate: 0.25 }
          ]
        );
        match('fuz',
          {
            v1: { value: 'fuzzy', rate: 0.75 },
            v2: { value: 'it is fuzzy', rate: 0.25 }
          }
        )
      
  • options

    optiontypedefaultdescription
    caseSensitiveBooleanfalseWhen true, character case matters (e.g. 'FZZ' will NOT match 'fuzzy'). When false, case is ignored.
    withScoreBooleanfalseWhen true, score is computed for matched strings. When false, score is always 1.
    withWrapperString or FunctionnullWhen provided, result contains wrapped — the original string with matched parts highlighted.
    String: template with {?} placeholder (e.g. '<b>{?}</b>'fzz in fuzzy'<b>f</b>u<b>zz</b>y')
    Function(word: String): String: (e.g. (word) => '<b>'+word+'</b>')
    withRangesBooleanfalseWhen true, result contains ranges — array of { begin: Number, end: Number } with matched character ranges.
    ratesArray or Object{}Weights for each item in where. Values must be > 0 and <= 1. See examples below.
      match('fuz',
        ['fuzzy', 'it is fuzzy'],
        { rates: [0.75, 0.25] }
      )
      // or
      match('fuz',
        ['fuzzy', 'it is fuzzy'],
        { rates: { 0: 0.75, 1: 0.25 } }
      )
      // equivalent to:
      match('fuz',
        {
          v1: { value: 'fuzzy', rate: 0.75 },
          v2: { value: 'it is fuzzy', rate: 0.25 }
        }
      )
    

Result

  • score - from 0.001 to infinity, lower is better. If withScore is false then score is always 1.

  • matches - present when where is Array or Object. An object where each key is the index/key and each value is:

    {
      score: Number,
      original: String,
      index: Number | String,
      [wrapped: String],
      [ranges: Array]
    }
    
  • wrapped - present if withWrapper is set. Contains the original string with matched parts wrapped.

  • ranges - present if withRanges is true. Array of { begin: Number, end: Number }.

import { match } from 'fuzzy-tools';

match('fzz', 'fuzzy', { withScore: true }); // { score: 1.74 }

match('fzz', ['fu', 'fuzza'], { withScore: true }) // { score: 1.74, matches: {1:{score: 1.74}} }

match('fzz', [{ value: 'fuzza', rate: 0.75 }, { value: 'fuzzy', rate: 0.10 }], { withScore: true })
// { score: 2.32, matches: {0: {score: 2.32}, 1: {score: 17.4}} }
// score = Min(1.74 / 0.75, 1.74 / 0.10)

match('fzz', 'fuzzy'); // { score: 1 }

match('fZZ', 'fuzzy', { caseSensitive: true }); // null

match('fZZ', 'fuzzy', { withWrapper: '<i>{?}</i>' });
// { score: 1, wrapped: '<i>f</i>u<i>zz</i>y' }

match('fZZ', ['fuzzy'], { withWrapper: '<i>{?}</i>' });
// { score: 1, matches: {0: {score: 1, original: 'fuzzy', index: 0, wrapped: '<i>f</i>u<i>zz</i>y'}}}

match('fZZ', { v: 'fuzzy' }, { withWrapper: '<i>{?}</i>' });
// { score: 1, matches: { v: {score: 1, original: 'fuzzy', index: 'v', wrapped: '<i>f</i>u<i>zz</i>y'}}}

match('fZZ', 'fuzzy', { withWrapper: w => `<b>${w}</b>` });
// { score: 1, wrapped: '<b>f</b>u<b>zz</b>y' }

match('fZZ', ['fuzzy'], { withWrapper: w => `<b>${w}</b>`, withScore: true });
// { score: 1, matches: {0: {score: 1, wrapped: '<b>f</b>u<b>zz</b>y', original: 'fuzzy'}}}

match('fZZ', 'fuzzy', { withRanges: true });
// { score: 1, ranges: [{begin: 0, end: 0}, {begin: 2, end: 3}]}

match('fZZ', ['fuzzy'], { withRanges: true });
// { score: 1, matches: {0: {score: 1, ranges: [{begin: 0, end: 0}, {begin: 2, end: 3}], original: 'fuzzy'}}}

Match in string

import { match } from 'fuzzy-tools';
// or
// import { matchString } from 'fuzzy-tools';

match('fzz', 'fuzzy', { withScore: true }); // { score: 1.74 }

match('fzz', 'fuzzy'); // { score: 1 }

match('fZZ', 'fuzzy', { caseSensitive: true }); // null

match('fZZ', 'fuzzy', { withWrapper: '<i>{?}</i>', withScore: true });
// { score: 1.74, wrapped: '<i>f</i>u<i>zz</i>y' }

match('fZZ', 'fuzzy', { withWrapper: w => `<b>${w}</b>` });
// { score: 1, wrapped: '<b>f</b>u<b>zz</b>y' }

match('fZZ', 'fuzzy', { withRanges: true });
// { score: 1, ranges: [{begin: 0, end: 0}, {begin: 2, end: 3}]}

Match in list of strings

import { match } from 'fuzzy-tools';
// or
// import { matchList } from 'fuzzy-tools';

match('fzz', ['fu', 'fuzza'], { withScore: true })
// { score: 1.74, matches: {1: {score: 1.74, original: 'fuzza', index: 1}} }

match('fzz', { v1: 'fu', v2: 'fuzza' })
// { score: 1, matches: {v2: {score: 1, original: 'fuzza', index: 'v2'}} }

match('fzz', [{ value: 'fuzza', rate: 0.75 }, { value: 'fuzzy', rate: 0.10 }], { withScore: true })
// {
//   score: 2.3708148148148145,
//   matches: {
//     0: { score: 2.3708148148148145, original: 'fuzza', rate: 0.75, index: 0 },
//     1: { score: 17.78111111111111, original: 'fuzzy', rate: 0.10, index: 1 }
//   }
// }
// score = min(2.3708148148148145, 17.78111111111111)

// the same but more compact
match('fzz', ['fuzza', 'fuzzy'], { rates: [0.75, 0.10], withScore: true })
// {
//   score: 2.3708148148148145,
//   matches: {
//     0: { score: 2.3708148148148145, original: 'fuzza', rate: 0.75, index: 0 },
//     1: { score: 17.78111111111111, original: 'fuzzy', rate: 0.10, index: 1 }
//   }
// }
// score = min(2.3708148148148145, 17.78111111111111)

match('fzz', {v1: { value: 'fuzza', rate: 0.75 }, v2: { value: 'fuzzy', rate: 0.10 } }, { withScore: true })
// {
//   score: 2.3708148148148145,
//   matches: {
//     v1: { score: 2.3708148148148145, original: 'fuzza', rate: 0.75, index: 'v1' },
//     v2: { score: 17.78111111111111, original: 'fuzzy', rate: 0.10, index: 'v2' }
//   }
// }
// score = min(2.3708148148148145, 17.78111111111111)

// the same but more compact
match('fzz', { v1: 'fuzza', v2: 'fuzzy' }, { rates: { v1: 0.75, v2: 0.10 }, withScore: true })
// {
//   score: 2.3708148148148145,
//   matches: {
//     v1: { score: 2.3708148148148145, original: 'fuzza', rate: 0.75, index: 'v1' },
//     v2: { score: 17.78111111111111, original: 'fuzzy', rate: 0.10, index: 'v2' }
//   }
// }
// score = min(2.3708148148148145, 17.78111111111111)

match('fZZ', ['fuzzy'], { caseSensitive: true });
// null

match('fZZ', ['fuzzy'], { withWrapper: '<i>{?}</i>' });
// { score: 1, matches: {0: {score: 1, index: 0, wrapped: '<i>f</i>u<i>zz</i>y', original: 'fuzzy'}}}

match('fZZ', ['fuzzy'], { withWrapper: w => `<b>${w}</b>`, withScore: true });
// { score: 1.74, matches: {0: {score: 1.74, index: 0, wrapped: '<b>f</b>u<b>zz</b>y', original: 'fuzzy'}}}

match('fZZ', ['fuzzy'], { withRanges: true });
// { score: 1.74, matches: {0: {score: 1.74, index: 0, ranges: [{begin: 0, end: 0}, {begin: 2, end: 3}], original: 'fuzzy'}}}

Filter function

filter(mask: String | Array<String>, items: Array, options: Object): Array - returns list of matched items.

Tests cases

Arguments

argstypedefaultnote
maskString or Array<String>nowhat you want to find ('fzz')
itemsArraynoitems list
optionsObject{...}additional options (see below)
  • mask - read more in Match function

  • items

    • Array of Strings
    • Array of Objects
  • options

    optiontypedefaultdescription
    extractString, Array, Object or FunctionnullRequired to filter Array of Objects. Specifies which field(s) to match against.
    String: field name — extracts that field and matches against the mask
    Array: field names — each field is matched with rate 1
    Object: { fieldName: rateNumber } — field name is key, rate is value
    Function(item): String or Array — returns String, Array of Strings, Object of Strings, or Array of { value: String, rate: Number }
    itemWrapperFunctionnullFunction(item, matchResult, context): any — transforms each matched item before pushing into result. If it returns a falsy value (false, null, undefined, '', 0), the item is skipped.
    context: { index: Number, result: Array }
    caseSensitiveBooleanfalseWhen true, character case matters (e.g. 'FZZ' will NOT match 'fuzzy'). When false, case is ignored.
    withScoreBooleanfalseWhen true, score is computed for matched strings. When false, score is always 1.
    withWrapperString or FunctionnullWhen provided, match result for each item contains wrapped — the extracted value with matched parts highlighted.
    String: template with {?} placeholder (e.g. '<b>{?}</b>')
    Function(word: String): String: (e.g. (word) => '<b>'+word+'</b>')
    withRangesBooleanfalseWhen true, match result for each item contains ranges — array of { begin: Number, end: Number }.

Filter strings list

import { filter } from 'fuzzy-tools';

const data = ['fuzzy', 'fazzy', 'furry', 'funny', 'fuuuuuzzer'];
filter('fZZ', data);
// ['fuzzy', 'fazzy', 'fuuuuuzzer'];

filter('fZZ', data, { itemWrapper: item => item.toUpperCase() });
// ['FUZZY', 'FAZZY', 'FUUUUUZZER'];

filter('fZZ', data, { withWrapper: '<{?}>', itemWrapper: (item, match) => match.wrapped });
// ['<f>u<zz>y', '<f>a<zz>y', '<f>uuuuu<zz>er'];

filter('fZZ', data, { extract: (item) => item.slice(0, 4) });
// ['fuzzy', 'fazzy'];

filter('FZZ', data, { caseSensitive: true });
// [];

filter('FZZ', data, { caseSensitive: true, extract: item => item.toUpperCase() });
// ['fuzzy', 'fazzy', 'fuuuuuzzer'];

Filter objects list

import { filter } from 'fuzzy-tools';

const data = [{ v: 'fuzzy' }, { v: 'funny' }, { v: 'fuuuuuzzer'}];

filter('fZZ', data, { extract: 'v' });
// [{ v: 'fuzzy' }, { v: 'fuuuuuzzer'}];

filter('fZZ', data, { extract: 'vvv' });
// [];

filter('fZZ', data, { extract: 'v', itemWrapper: item => item.v });
// ['fuzzy', 'fuuuuuzzer'];

filter('fZZ', data, { extract: 'v', withWrapper: '<{?}>', itemWrapper: (item, m) => m.wrapped });
// ['<f>u<zz>y', '<f>uuuuu<zz>er'];

// if extract contains more than 1 field, then result of matching will contain matches array
filter('fZZ', data, {
    extract: ['v', 'v2'],
    withWrapper: '<{?}>',
    itemWrapper: (item, m) => m.matches[0].wrapped
});
// ['<f>u<zz>y', '<f>uuuuu<zz>er'];

Benchmark results

Compare next libraries

Methodology

  1. generate pairs mask (random length) and string (255 or 5000 chars length). 5000 pairs.
  2. call match function from each library
  3. compare results

Results

It is more real case - 5000 items with 255 length string

fuzzy-tools vs others in real case

It is test case - 5000 items with 2000 length string. And it is not mistake, it is real results.

fuzzy-tools vs others

Benchmark project

https://codesandbox.io/s/fuzzytoolsbenchmarkexample-t9fk1?file=/src/index.js