mrkeyoor.com_
Sun 20 Sept 12:45 UTC
npmUtilsupdated 20 Sept 2026

mnemonist review

Mnemonist 0.40.4 puts dozens of mutable JavaScript data structures in one package, ranging from ordinary heaps, queues, tries, LRU maps, and multisets to BK trees, VP trees, Bloom filters, suffix arrays, interval indexes, and fixed-capacity buffers. It deliberately omits graphs. Each class exposes its own capacity, iteration, deletion, and size semantics rather than forcing one universal collection interface. Release 0.40.4 changes declarations, correcting what LRUCache.setpop and LRUMap.setpop return. Our complete-package browser build was 101.4 KB minified, so modular import behavior matters.

Verdict

Our install of Mnemonist 0.40.4 took 0.8 seconds and 1 MB with no audit findings, but an all-exports browser build reached 24.3 KB gzipped. It pays off when one service genuinely needs several unusual structures; use a focused package for one heap, deque, or cache, especially when ESM subpath imports are required.

We installed it

Lab card: what happened when we installed mnemonistScreenshot of mnemonist documentation
Install✓ · 0.8s2 packages on disk · 1 MB
ImportESM import works · require() works · CommonJS package with exports map
Browser24.3 KBgzipped (101.4 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does mnemonist install cleanly?

Yes. In a fresh container with an empty cache, npm install mnemonist finished in 0.8s, leaving 2 packages and 1 MB on disk. npm audit reported no known vulnerabilities.

How much does mnemonist add to a browser bundle?

24.3 KB gzipped (101.4 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does mnemonist work with both ESM and CommonJS?

Yes. Both import 'mnemonist' and require('mnemonist') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does mnemonist include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

mnemonist or denque: which should you use?

denque: Choose it when a growable double-ended queue is the whole requirement. Our install of Mnemonist 0.40.4 took 0.8 seconds and 1 MB with no audit findings, but an all-exports browser build reached 24.3 KB gzipped.

When should you not use mnemonist?

Native ESM must import an individual subpath; the exports map supplies subpaths only for require, while root named ESM imports work

API stability4/5Heap, Queue, Trie, LRUCache, MultiMap, MultiSet, and fixed-container constructors remain recognizable across recent releases. The 0.40 line added named ESM exports at the root, while 0.40.4 repaired setpop type declarations without changing runtime semantics. The package is still below 1.0, and its classes deliberately differ on size, dimension, delete, remove, and overflow, so interface consistency cannot be assumed.
Docs4/5Each listed structure has its own documentation page with constructors, operations, iteration examples, and frequent complexity notes. The README groups the collection by use and explicitly excludes graphs. What is missing is comparison guidance: overwrite versus throw behavior sits on individual pages, naming differences require cross-reading, and the docs do not call out that native ESM cannot use the wildcard subpaths exposed to require().
Maintenance3/5The repository is unarchived and was last pushed on 2026-04-30, the same date as version 0.40.4. GitHub reports 78 open issues and pull requests. Recent releases addressed ESM root exports, side-effect metadata, and TypeScript declarations, including the setpop fix. Maintenance exists, though release intervals are wider than high-volume infrastructure packages and the public version remains 0.x.
Ecosystem4/5npm counted 15,229,019 downloads in the latest completed week, and GitHub reported 2,434 stars. Few JavaScript packages combine everyday collections with metric search, spatial indexes, succinct structures, and text indexes under bundled types. This is a leaf library rather than a plugin ecosystem. Its practical integration gap is packaging: ESM works at the root, while per-structure wildcard paths are available only to CommonJS require().

Use it if

  • One service needs several specialist containers and reviewing one MIT package is preferable to collecting many tiny modules
  • A hot path needs a ring buffer, typed vector, bit collection, or sparse set with capacity chosen up front
  • Search code needs a typed BK tree, VP tree, SymSpell index, suffix array, or static interval tree
  • CommonJS callers can load a single implementation through paths such as require('mnemonist/heap')
Skip it if

Setup reality

We installed Mnemonist 0.40.4 in 0.8 seconds on an unprivileged Node 22 container. Two packages used 1 MB afterward. Mnemonist was 648 KB unpacked, declared 1 dependency and 0 peers, bundled its TypeScript declarations, and used the MIT license. npm audit found 0 known vulnerabilities. require() and root-level ESM import both worked through a CommonJS package with an exports map. Importing everything bundled to 101.4 KB minified and 24.3 KB gzipped.

Module format has a sharp edge. require('mnemonist/heap') loads one CommonJS file, but import Heap from 'mnemonist/heap' fails because the wildcard export has no import condition. ESM uses named exports from 'mnemonist'. The package declares no side effects, which helps bundlers discard unused code, yet our 24.3 KB gzip figure shows why a frontend team should inspect its actual output instead of assuming tree shaking happened.

Fixed containers do not share one overflow contract. new CircularBuffer(Array, 100) overwrites its oldest value after 100 entries; a FixedDeque with the same capacity throws. LRUCache and LRUMap also stop at a fixed capacity and have no TTL policy. Their basic forms omit keyed deletion, which is supplied by WithDelete variants. setpop returns an evicted pair only when an insertion displaced something, and 0.40.4 corrected that TypeScript return declaration.

Names must be read per class. MultiSet.size totals occurrences while dimension counts distinct values. DefaultMap.get() inserts a generated default, making an apparent read mutate state. Trie.find() returns every prefix match without a result cap. BloomFilter cannot enumerate or delete values and may say an unseen item is present. BK and VP trees can miss valid neighbors if the supplied distance function violates metric rules.

Patterns

Use the root for ESM and subpaths for CommonJS import-structure

// CommonJS can load one file
const Heap = require('mnemonist/heap');

// ESM uses the package root
import { LRUCache, Trie } from 'mnemonist';

Version 0.40.4 exposes named ESM exports only at the root; its wildcard subpaths are require-only.

Pop the lowest-priority number first use-priority-queue

const Heap = require('mnemonist/heap');

const jobs = new Heap((a, b) => a.priority - b.priority);
jobs.push({ id: 'slow', priority: 10 });
jobs.push({ id: 'urgent', priority: 1 });

const next = jobs.pop();
console.log(next.id); // urgent

The comparator follows Array.sort conventions, and this ascending comparison places the smallest priority at the root.

Keep 500 entries by recency cache-recent-values

const LRUCache = require('mnemonist/lru-cache');

const cache = new LRUCache(500);
cache.set('user:7', { id: 7 });

const value = cache.get('user:7'); // refreshes recency
const untouched = cache.peek('user:7');
const present = cache.has('user:7');

This cache evicts by fixed count, has no time expiry, and needs LRUCacheWithDelete for explicit keyed removal.

Capture an LRU eviction during insertion observe-lru-eviction

const LRUMap = require('mnemonist/lru-map');

const cache = new LRUMap(2);
cache.set('a', 1);
cache.set('b', 2);

const evicted = cache.setpop('c', 3);
// evicted is ['a', 1]

The 0.40.4 declaration describes the pair correctly; setpop returns null when no entry was displaced.

Collect every trie match for a prefix autocomplete-prefix

const TrieMap = require('mnemonist/trie-map');

const words = new TrieMap();
words.set('paper', 1);
words.set('paperback', 2);
words.set('paint', 3);

const matches = words.find('pape');

find() has no result limit, so short prefixes over a large dictionary need an application-side bound.

Store distinct values under one key group-multiple-values

const MultiMap = require('mnemonist/multi-map');

const tags = new MultiMap(Set);
tags.set('js', 'post-1');
tags.set('js', 'post-2');
tags.set('js', 'post-2');

console.log(tags.get('js'));
console.log(tags.size, tags.dimension); // 2, 1

Set backing removes duplicate post-2, leaving size at 2 while dimension remains the single js key.

Increment and remove occurrence counts count-occurrences

const MultiSet = require('mnemonist/multi-set');

const paths = MultiSet.from(['/a', '/a', '/b']);
paths.add('/b', 2);

console.log(paths.count('/b')); // 3
console.log(paths.top(2));
paths.remove('/a');

remove() subtracts one occurrence, delete() drops the whole entry, and top() performs sorting on each call.

Populate a default array on get() create-default-map

const DefaultMap = require('mnemonist/default-map');

const byOwner = new DefaultMap(() => []);
for (const task of tasks) {
  byOwner.get(task.owner).push(task);
}

const existing = byOwner.peek('missing');

A missing get() mutates this map by storing the factory result; peek() observes without inserting.

Retain only the latest three values keep-recent-window

const CircularBuffer = require('mnemonist/circular-buffer');

const recent = new CircularBuffer(Array, 3);
recent.push('a');
recent.push('b');
recent.push('c');
recent.push('d');

console.log(recent.toArray()); // ['b', 'c', 'd']

The fourth push replaces a without warning; FixedDeque is the alternative when reaching capacity must raise.

Rule out an unseen key with a Bloom filter test-probable-membership

const BloomFilter = require('mnemonist/bloom-filter');

const seen = new BloomFilter(100_000);
seen.add('invoice:42');

if (!seen.test('invoice:99')) {
  console.log('definitely unseen');
}

false proves absence, while true is only probable membership; enumeration and deletion are unavailable.

Union fixed numeric identifiers group-connected-indices

const StaticDisjointSet = require('mnemonist/static-disjoint-set');

const groups = new StaticDisjointSet(6);
groups.union(0, 1);
groups.union(1, 2);

console.log(groups.connected(0, 2)); // true
console.log(groups.dimension); // 4

The universe is the integers 0 through 5 here, so strings and sparse IDs need a separate index mapping.

Search a BK tree within distance one search-metric-space

const BKTree = require('mnemonist/bk-tree');
const editDistance = require('./edit-distance');

const tree = BKTree.from(
  ['book', 'books', 'cake'],
  editDistance,
);

const matches = tree.search(1, 'book');

Incorrect metric behavior can prune valid matches; returned item-distance objects also have no sorted-order promise.

Alternatives

PackageRegistryPick it when
denquenpmChoose it when a growable double-ended queue is the whole requirement.
heap-jsnpmChoose it for a focused heap API whose native ESM path is straightforward.
lru-cachenpmChoose it when eviction also needs TTL, stale values, fetch coordination, size limits, or disposal.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.