Skip to content

Getting Started ​

sorted-collections gives JavaScript/TypeScript three data structures that keep themselves sorted as you mutate them, with zero runtime dependencies:

  • SortedList — a list that keeps insertion order sorted automatically.
  • SortedSet — a sorted set with no duplicates, plus set-theory operations.
  • SortedMap — a dictionary ordered by key.

All three are backed by the same "list of lists" (bucketed array) technique Python's sortedcontainers uses, and work equally well from plain JavaScript or TypeScript.

Prerequisites ​

Node.js ^20.19.0 or >=22.12.0.

Installation ​

sh
npm i sorted-collections
sh
pnpm add sorted-collections
sh
yarn add sorted-collections

First example of each ​

ts
import { SortedList, SortedSet, SortedMap } from 'sorted-collections';

const scores = new SortedList<number>();
scores.add(42);
scores.add(7);
scores.add(99);
[...scores]; // [7, 42, 99]

const tags = new SortedSet<string>(['b', 'a', 'c']);
tags.has('b'); // true

const byPrice = new SortedMap<number, string>();
byPrice.set(101.5, 'order-1');
byPrice.set(99.75, 'order-2');
[...byPrice.keys()]; // [99.75, 101.5]

Building from an existing iterable ​

Passing an iterable to the constructor builds in bulk — sorts once, then cuts directly into buckets — rather than inserting one element at a time. SortedList.from, SortedSet.from, and SortedMap.from are equivalent sugar, paralleling Array.from:

ts
const scores = SortedList.from([42, 7, 99]); // same as new SortedList([42, 7, 99])
const tags = SortedSet.from(['b', 'a', 'c', 'a']); // dedupes: ['a', 'b', 'c']

See Benchmarks for what bulk construction is worth at scale.

Custom comparators ​

Without a comparator, elements sort by natural order for number and string. Any other type requires one — TypeScript enforces this at compile time:

ts
interface Person {
  name: string;
  age: number;
}

// Compile error without a comparator: Person isn't naturally orderable.
const byAge = new SortedList<Person>([], {
  comparator: (a, b) => a.age - b.age,
});

has/remove/delete on all three structures compare by the comparator, not reference identity — two different object instances that compare equal are treated as the same element/key.

Range queries ​

irange (by value) and islice (by position) work the same way across all three structures:

ts
// Everyone scoring between 50 and 100, inclusive:
[...scores.irange(50, 100)];

// Order book: every order priced at 100 or less:
[...byPrice.irange(undefined, 100)];

// First three entries by position:
[...scores.islice(0, 3)];

Next steps ​

Released under the MIT License.