string-natural-compare
string-natural-compare is a CommonJS comparator for JavaScript Array.sort. It orders digit runs by their numeric value, so img2 comes before img10, without converting huge numbers to JavaScript Number. It can fold case and apply a caller-supplied character alphabet, and it throws when either compared value is not a string. It is a fast deterministic alphanumeric sorter, not a locale-aware collator, semantic version comparator, filesystem walker, or object sorting library.
Still a capable tiny comparator for machine-oriented strings and arbitrarily long digit runs. New user-facing applications should start with `Intl.Collator({numeric: true})`; install this only when its deterministic ASCII-like order or explicit alphabet is the actual requirement.
Use it if
- You sort filenames, room numbers, build labels, or other ASCII-heavy identifiers containing digit runs
- Your strings may contain integers larger than Number.MAX_SAFE_INTEGER and must still sort by magnitude
- You need a custom explicit character order that is stable across machines
- A small dependency-free CommonJS comparator fits your existing runtime and build
- You need user-language collation, accents, punctuation rules, or locale numeric sorting: this implementation compares JavaScript character codes outside its custom alphabet, while Intl.Collator handles locale rules
- You use ESM or strict TypeScript and want first-party support: version 3.0.1 is CommonJS, has no exports map, and publishes no type declarations
- You sort mixed null, number, or object values: the source explicitly throws TypeError unless both comparator arguments are strings, so normalization is mandatory
- You need semantic version precedence: natural digit ordering does not implement prerelease, build metadata, or range rules from the SemVer specification
- You generate many custom alphabets dynamically: the source caches an index map for every alphabet string forever, and the README warns that putting digits in a custom alphabet causes undefined behavior
Setup reality
npm install string-natural-compare adds no dependencies, peers, native build, credentials, or configuration. Version 3.0.1 exports one CommonJS function, so require('string-natural-compare') is the native form. ESM consumers depend on Node or bundler CommonJS interop and should default-import only after testing their target. No TypeScript declarations are included; add a small local declaration or use a separately maintained type package if your dependency policy permits one. Pass the function directly to Array.sort for case-sensitive strings. For case-insensitive comparison, wrap it and pass {caseInsensitive: true}; equal folded strings return 0, so their relative order relies on modern JavaScript's stable sort. Both inputs must already be strings. Decide explicitly where nulls go and which property to compare before invoking it. The algorithm recognizes ASCII digits 0 through 9, ignores numeric overflow by comparing digit-run lengths and characters, and uses UTF-16 code-unit ordering for other characters. That is deterministic but not linguistic. A custom alphabet rebuild is cached by its full string, which helps repeated fixed options but can retain memory if alphabets come from unbounded user input. Do not include digits in that alphabet because the README calls the behavior undefined. Array.sort mutates its array, and comparators run many times, so precompute lowercase or composite keys for large object lists as the README recommends rather than repeatedly transforming inside the callback.
Patterns
Sort filenames in natural ordersort-filenames
const naturalCompare = require('string-natural-compare');
const files = ['img10.png', 'img2.png', 'img1.png'];
files.sort(naturalCompare);
// ['img1.png', 'img2.png', 'img10.png']Array.sort mutates files. Use files.toSorted(naturalCompare) or [...files].sort(...) when the original order must survive.
Return a naturally sorted copysort-without-mutation
const sortedFiles = files.toSorted(naturalCompare);toSorted is available in modern JavaScript runtimes. Use a spread copy before sort when supporting older engines.
Ignore case during comparisonsort-case-insensitive
const options = { caseInsensitive: true };
const compareInsensitive = (a, b) => naturalCompare(a, b, options);
const labels = ['B2', 'a10', 'A2'].sort(compareInsensitive);Strings equal after lowercasing compare as 0, so their input order is preserved by stable modern Array.sort.
Sort records by one natural keysort-objects-by-property
rooms.sort((a, b) => naturalCompare(a.room, b.room));Both properties must be strings. Normalize missing values before calling the comparator or it throws TypeError.
Sort by street, then roomsort-multiple-fields
rooms.sort((a, b) =>
naturalCompare(a.street, b.street, { caseInsensitive: true }) ||
naturalCompare(a.room, b.room)
);Comparator zero falls through to the next field. Keep each field's case and locale policy explicit.
Precompute keys for a large object listprecompute-sort-keys
const keyed = cars.map((car) => ({
value: car,
key: `${car.make} ${car.model}`.toLowerCase(),
}));
keyed.sort((a, b) => naturalCompare(a.key, b.key));
const sortedCars = keyed.map(({ value }) => value);The README recommends precomputation when transformation would otherwise repeat during many comparator calls.
Compare integer strings beyond Number precisionsort-large-integers
const ids = [
'265812277985321589735871687040841',
'1165874568735487968325787328996865',
];
ids.sort(naturalCompare);The algorithm compares digit-run lengths and digits rather than converting to Number, so it avoids precision loss.
Reverse natural ordersort-descending
versions.sort((a, b) => naturalCompare(b, a));This reverses the comparator but still does not implement semantic version rules for prereleases or build metadata.
Place missing values lasthandle-null-values
function compareNullable(a, b) {
if (a == null) return b == null ? 0 : 1;
if (b == null) return -1;
return naturalCompare(String(a), String(b));
}
values.sort(compareNullable);The package accepts strings only. Choose an explicit null policy instead of blindly converting null to the text 'null'.
Apply an explicit character alphabetuse-custom-alphabet
const russianAlphabet = 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя';
const options = { alphabet: russianAlphabet };
letters.sort((a, b) => naturalCompare(a, b, options));Do not put digits in the custom alphabet; the README says that causes undefined behavior. Reuse fixed alphabet strings so their cached maps remain bounded.
Add a local TypeScript declarationtype-commonjs-module
// types/string-natural-compare.d.ts
declare module 'string-natural-compare' {
interface Options { caseInsensitive?: boolean; alphabet?: string }
function naturalCompare(a: string, b: string, options?: Options): number;
export = naturalCompare;
}Version 3.0.1 does not publish declarations. Keep the shim aligned with the README's two options.
Use locale-aware numeric sorting insteaduse-native-collator
const collator = new Intl.Collator(undefined, {
numeric: true,
sensitivity: 'base',
});
const sorted = labels.toSorted(collator.compare);Intl.Collator is usually the better user-facing choice because it understands locale collation; results can differ by locale and runtime data.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| natural-compare-lite | npm | You want another tiny natural comparator with a long-established browser-oriented implementation |
| natural-orderby | npm | You need iteratee-based ordering of objects, multiple fields, and ascending or descending directions |
| natsort | npm | You want configurable natural sorting with additional date, hexadecimal, and insensitive modes |
| javascript-natural-sort | npm | You maintain an older project already built around its comparator and case-insensitive flag |