npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

lightning-levenshtein

v0.0.6

Published

Blazing-fast Levenshtein distance with bit-parallel Myers algorithm and early exits.

Readme

⚡ lightning-levenshtein

npm downloads bundle size license stars types

Fast Levenshtein distance in pure JavaScript. Compact default API for general-purpose edit distance, plus opt-in subpaths for maximum throughput and Unicode-width character tables.

Features

  • Specialized kernels for very short strings
  • Precompiled bit-parallel dispatch for short inputs
  • Fixed-width Myers variants for medium inputs
  • Generalized Myers fallback for large inputs
  • Zero runtime dependencies
  • Works in Node.js and browsers

Install

pnpm add lightning-levenshtein
# or
npm install lightning-levenshtein

Node.js consumers require Node 18 or newer. Browser consumers can use a bundler-facing source entrypoint or an explicit /min bundle.


Quick start

import { distance, distanceMax, closest } from "lightning-levenshtein";

distance("kitten", "sitting");                 // 3
distanceMax("kitten", "sitting", 2) > 2;       // true (over threshold)
closest("kitten", ["kitchen", "sitting"]);     // "kitchen"

API

The package exposes four logical entrypoints, each with an ESM-source path for bundlers and a pre-built minified path for unbundled <script type="module"> use:

{
  "exports": {
    ".":             { "import": "./src/index.js",       "default": "./dist/lightning-levenshtein.min.js" },
    "./min":         {                                    "default": "./dist/lightning-levenshtein.min.js" },
    "./v2":          { "import": "./src/v2/index.js",    "default": "./dist/lightning-levenshtein-v2.min.js" },
    "./v2/min":      {                                    "default": "./dist/lightning-levenshtein-v2.min.js" },
    "./unicode":     { "import": "./src/unicode.js",     "default": "./dist/lightning-levenshtein-unicode.min.js" },
    "./unicode/min": {                                    "default": "./dist/lightning-levenshtein-unicode.min.js" },
    "./profiles":     { "import": "./src/profiles.js",   "default": "./dist/lightning-levenshtein-profiles.min.js" },
    "./profiles/min": {                                    "default": "./dist/lightning-levenshtein-profiles.min.js" }
  }
}

The ., ./v2, ./unicode, and ./profiles entrypoints route bundlers at ESM source so unused code can be tree-shaken. Explicit /min subpaths expose the pre-built Closure bundles for consumers that want them directly.

Default API

import { distance, distanceMax, closest } from "lightning-levenshtein";

The stable core API. Bundlers receive ESM source; unbundled consumers can use lightning-levenshtein/min to point at the pre-built bundle directly.

distanceMax(a, b, maxDistance) returns the exact distance when it is within the effective threshold. When the threshold is exceeded, it returns a value greater than the threshold rather than a sentinel such as -1. A fractional threshold between 0 and 1 is interpreted relative to the original length of a and rounded up.

closest(str, candidates, maxDistance) returns the first best candidate within the threshold. Candidate order breaks equal-distance ties.

Max-throughput API

The /v2 subpath exposes the larger max-throughput runtime:

import { levenshteinLightning } from "lightning-levenshtein/v2";

Bundlers receive src/v2/index.js; lightning-levenshtein/v2/min exposes the pre-built bundle. The v2 runtime uses more aggressive length-based dispatch, tiny-string fast paths, precompiled 32-bit kernels, fixed-width Myers variants, and a generalized large-input fallback. Its specialized kernels currently allocate 6 MiB of UTF-16 code-unit PEQ payload per module realm or worker. Choose it when throughput matters more than the extra JavaScript and memory footprint.

Unicode API

Full-width UTF-16 code-unit distance:

import { distanceUnicode } from "lightning-levenshtein/unicode";

Use this path when your strings can contain code units above 255, such as many BMP characters, and you need consistent full-width behavior at every input length. It is intentionally pre-routed so the hot loop does not scan each call to select a character table. Use lightning-levenshtein/unicode/min for the pre-built bundle.

Profiles API

Create a reusable distance function with an explicit character-table width:

import { createDistance } from "lightning-levenshtein/profiles";

const distanceAscii = createDistance({ profile: "ascii" });
const distanceLatin1 = createDistance({
  profile: "latin1",
  outOfRange: "assume-valid"
});

Available profiles are ascii, latin1, and codeUnit. The default outOfRange: "throw" policy validates the selected range. Use assume-valid only when inputs were validated or normalized before the call. lightning-levenshtein/profiles/min exposes the pre-built bundle.


Notes

Which one should I pick?

Use the default package entrypoint if you want the stable general-purpose API with the smallest production build:

import { distance, distanceMax, closest } from "lightning-levenshtein";

Use the v2 subpath if you specifically want the specialized levenshteinLightning runtime and are comfortable with the larger payload:

import { levenshteinLightning } from "lightning-levenshtein/v2";

Use the unicode subpath if you need consistent full-width UTF-16 code-unit behavior:

import { distanceUnicode } from "lightning-levenshtein/unicode";

Use the profiles subpath when you want an isolated distance function with an explicit character range and predictable per-instance memory:

import { createDistance } from "lightning-levenshtein/profiles";

const distanceAscii = createDistance({ profile: "ascii" });
const distanceLatin1 = createDistance({
  profile: "latin1",
  outOfRange: "assume-valid"
});

Profiles are ascii (0..127), latin1 (0..255), and codeUnit (0..65535). The default outOfRange policy is throw. Use assume-valid only when the application has already validated or normalized both strings; out-of-profile input under that policy has undefined results.

Character table strategy

The default API uses a 256-entry PEQ table through 64 code units, so those tiers assume ASCII/Latin-1-style input. Its long-string fallback uses two full-width PEQ lanes. The default therefore optimizes common short and medium inputs but does not promise one uniform character-table width across every tier.

The unicode subpath exposes distanceUnicode, which is backed by a full UTF-16 code-unit PEQ table at every tier. That path is intentionally separate so callers can opt into consistent wider character support without adding per-call detection to the hot path.

The design direction is:

  • keep distance fast and pre-routed for common ASCII/Latin-1 workloads
  • expose full-width UTF-16 code-unit behavior through the deliberate /unicode subpath
  • share kernel implementations by binding the PEQ table before entering the hot function
  • avoid naive automatic routing that scans both strings on every call

The /profiles factory implements that direction without changing the default entrypoint. Each returned function owns its mutable tables and scratch state, so create one per worker or independent execution context and reuse it synchronously.

The repository design note Levenshtein Use Cases and Text Profiles covers real-world workloads, precise comparison units, current per-worker memory costs, and configurable-profile design.

PEQ memory inventory

Static typed-array payload currently scales with each worker or module realm:

| Entrypoint | PEQ payload per worker | 4 workers | 8 workers | | --- | ---: | ---: | ---: | | default | about 513 KiB | about 2 MiB | about 4 MiB | | /unicode | 768 KiB | 3 MiB | 6 MiB | | /v2 | 6 MiB | 24 MiB | 48 MiB |

Profile factories allocate three PEQ lanes per returned function: 1.5 KiB for ASCII, 3 KiB for Latin-1, or 768 KiB for full code-unit coverage.

These figures count PEQ typed-array payload, not total process memory, JavaScript objects, or retained scratch buffers. Module tables and profile instances are reused by synchronous calls; separate workers load separate state.

Dense integer sequences remain the preferred future shape for DNA, proteins, phonemes, transcript words, and custom alphabets because they can use a table sized to the encoded symbol range. That separate token API is planned, not currently published.

See the checked-in stable-core integration plan for the implemented API, validation policies, file scope, test matrix, worker benchmarks, and v2 deferral criteria. The technical reflection and benchmark-hardening sprint record the broader architectural assessment and next measurement work.

Dispatch strategy

The runtime selects the cheapest correct kernel for the current input size.

  • 1–32 chars: precompiled bit-parallel kernels
  • 33–64 chars: fixed-width Myers specialization
  • 65–96 chars: fixed-width Myers specialization
  • 97–128 chars: fixed-width Myers specialization
  • 129–224 chars: generalized macro-block Myers dispatch
  • 225–256 chars: fixed-width Myers specialization
  • 257–512 chars: generalized macro-block Myers dispatch
  • 513+ chars: large-input generalized Myers dispatch

This keeps tiny inputs fast without sacrificing larger-input performance.

Benchmark

The benchmark harness generates the same string pairs for every library at each tested length and seed.

Node v24.11.0 on win32 x64, 13th Gen Intel(R) Core(TM) i5-13600K. Results generated 2026-07-16.

Methodology:

  • 500 random equal-length string pairs per test size
  • 3 seeds: 1337, 7331, 20250321
  • 500 ms measurement window per seed
  • 3 warm-up rounds before timing
  • alphabet: A-Z, a-z, 0-9
  • reported table values: mean ops/ms across 3 seeds

Mean ops/ms:

| Test Target | N=1 | N=2 | N=4 | N=8 | N=16 | N=32 | N=64 | N=128 | N=256 | N=512 | N=1024 | |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| | lightning-levenshtein-v2 | 176959 | 87310 | 45325 | 29719 | 11565 | 6559 | 1582 | 557.3 | 136.1 | 34.14 | 10.22 | | lightning-levenshtein-v1 | 89792 | 59605 | 40893 | 25256 | 8702 | 5080 | 1211 | 419.8 | 123.0 | 33.25 | 8.580 | | fastest-levenshtein | 95025 | 73865 | 43340 | 22888 | 8073 | 4404 | 1024 | 288.2 | 77.55 | 19.27 | 4.968 | | js-levenshtein | 100407 | 86948 | 22572 | 10990 | 3226 | 915.9 | 238.8 | 60.73 | 15.77 | 4.014 | 1.004 | | leven | 73872 | 43095 | 21606 | 8014 | 1778 | 415.8 | 113.5 | 29.13 | 7.573 | 1.908 | 0.480 | | levenshtein-edit-distance | 116046 | 56325 | 24109 | 8368 | 1776 | 394.7 | 104.9 | 26.94 | 6.886 | 1.750 | 0.429 |

Relative throughput vs fastest-levenshtein. This chart normalizes fastest-levenshtein to 100% at each string length and shows every other library relative to that baseline. Use it for an apples-to-apples comparison against the package most people already know. Values above 100% mean faster than fastest-levenshtein; values below 100% mean slower.

Relative performance vs fastest-levenshtein

Throughput across input sizes. Mean ops/sec shown on a log-scaled Y axis across the full tested range.

Levenshtein throughput by string length

Relative throughput, lightning-levenshtein only. Same baseline as above, narrowed to just lightning-levenshtein for a clearer read.

Relative performance vs second-fastest competitor

Rank by input length. Where each library ranks at each tested string length. Useful because raw throughput can be noisy to read at a glance, while rank makes the ordering obvious. If a library is consistently ranked first across the range, you can see that immediately without squinting at the absolute numbers.

Rank by string length

Results

  • lightning-levenshtein-v2 records the highest mean throughput in this checked-in Node benchmark at every tested length.
  • Winning lengths: N=1, N=2, N=4, N=8, N=16, N=32, N=64, N=128, N=256, N=512, N=1024.
  • At N=1024, mean throughput is 10.22 ops/ms versus 4.968 ops/ms for fastest-levenshtein.
  • At N=32, mean throughput is 6559 ops/ms versus 4404 ops/ms for fastest-levenshtein.
  • At N=8, mean throughput is 29719 ops/ms versus 22888 ops/ms for fastest-levenshtein.

Reproducing the benchmark

pnpm run bench:packages:verify
pnpm run bench:packages:promotion:check

Qualification measurements require a settled host and three separate raw repetitions. The checked-in aggregate must then be promoted explicitly before pnpm run bench:packages:render can update this section. See the package benchmark qualification workflow and evidence index.

Project layout

src/v2/
  index.js
  myers32-unrolledA.js
  myers_64.js
  myers_96.js
  myers_128.js
  myers_256.js
  myers_x64.js
  myers_x128.js

bench/bolt/
  experimental and historical kernel variants

bench/packages/
  run-bench.js
  render-readme-table.js
  render-readme-line-chart.js
  render-readme-rank-chart.js
  render-relative-bar-chart.js
  render-readme-relative-fastest-chart.js
  results.json

codegen/tools/
  generateMyers32-A.cjs  # owns the production v2 short-string table
  generateMyers32-B.cjs  # experimental comparison generator

Run pnpm run codegen:myers32:a after changing the production Myers32 generator. The command refreshes both the comparison artifact and src/v2/myers32-unrolledA.js; pnpm run codegen:check verifies that neither output has drifted.


License

Source-available under AGPL-3.0 with WATT3D Additional Terms. The project is not represented as OSI-approved open source because the additional terms restrict commercial and AI-training uses. See LICENSE, ADDITIONAL_TERMS.md, and the licensing position. The legal texts control; commercial use outside the public terms requires a separate WATT3D license. © WATT3D.