micro-memoize
A blazing-fast, tiny memoization library with configurable LRU caching, expiration, and cache-event hooks.
Repository Health
Technical Analysis
micro-memoize is a lightweight JavaScript/TypeScript memoization library that wraps any function in a cache-backed proxy, returning stored results for repeated argument combinations instead of recomputing them. It ships as ESM, CJS, and UMD builds with full TypeScript types, and its only runtime dependencies are fast-equals and fast-stringify.
Beyond simple single-value memoization, it supports LRU caches of arbitrary size, custom key equality (exact, shallow, or deep), key serialization, argument-count limits, time-based expiration, forced cache-busting, and an event emitter for observing cache hits, additions, updates, and deletions. Benchmarks bundled in the repo show it consistently outperforming lodash, ramda, underscore, mem, memoizee, memoizerific, and several other popular memoization packages across a range of argument shapes.
What You Get
- Drop-in
memoize(fn, options)wrapper with full TypeScript inference for the return type and cache shape - Configurable LRU cache via
maxSize, from single-entry (the default) up to any bound, with automatic least-recently-used eviction - Multiple key-equality strategies: exact (
Object.is), shallow, deep, a custom comparator, or full-key serialization - Time-based
expiresoption with dynamic/conditional expiration andasyncpromise-rejection auto-eviction - A
cache.on('add' | 'delete' | 'hit' | 'update')event emitter for observability, plus opt-in namedstatsNameusage tracking - ESM, CJS, and UMD builds with zero-config TypeScript types
Common Use Cases
- Caching expensive pure computations (parsing, formatting, derived-data transforms) keyed by their arguments
- Memoizing selector/derived-state functions in state-management layers to avoid recomputation on unrelated updates
- Wrapping async data-fetching functions with
async: trueso failed promises are automatically evicted instead of poisoning the cache - Building bounded caches for hot paths where only the last N distinct inputs matter, via
maxSize - Instrumenting cache hit/miss rates in production via
statsNameand the stats API to validate memoization is actually helping
Under The Hood
Architecture
The public surface is a single memoize() factory in src/index.ts that constructs a Cache instance (src/Cache.ts) and returns a wrapped function; when maxSize is left at its default of 1, a dedicated single-entry code path (cache.z) skips the linked-list traversal, recency updates, and size bookkeeping that the general multi-entry path (cache.g/cache.u/cache.n) performs, so the common case never pays for LRU machinery it doesn’t need. Optional concerns are opt-in and lazily instantiated: ExpirationManager (src/expires.ts) and StatsManager (src/stats.ts) are only created when options.expires or options.statsName is set, and a CacheEventEmitter is only attached the first time cache.on() is called, so a plain memoized function carries none of that overhead. Key transformation composes transformKey, maxArgs (src/maxArgs.ts), and serialize (src/serialize.ts) into a single reduced function applied in a fixed, documented order, keeping the cache’s core get/set logic decoupled from how keys are shaped.
Tech Stack
Written in TypeScript (about 89% of the codebase) compiled via Rollup into ESM, CJS, and UMD builds, each with its own generated .d.ts/.d.mts/.d.cts types declared through a conditional exports map in package.json. The only runtime dependencies are fast-equals (for shallow/deep key-item comparison) and fast-stringify (for the serialize option); everything else — @planttheidea/build-tools, release-it, tinybench, and a long list of competing memoization libraries (lodash, ramda, underscore, mem, memoizee, memoizerific, lru-memoize, fast-memoize) — is a devDependency used for building, releasing, or benchmarking against alternatives. It targets Node 6+ and older browsers (IE9+/Safari 6+) despite the modern TypeScript-first source.
Code Quality
The __tests__ directory has a dedicated Vitest test file per feature area (async, expires, forceUpdate, isKeyEqual, isKeyItemEqual, maxArgs, serialize, stats, plus the core index), giving each configuration option its own focused suite rather than one monolithic test file. ESLint runs with typescript-eslint and eslint-plugin-import at --max-warnings=0, Prettier enforces formatting, and tsc --noEmit gates types; internal fields use short, single/double-letter property names (c, h, t, k, o, p, s) for minification, each documented with a JSDoc comment spelling out what the abbreviation stands for. No CI workflow configuration is present in the repository itself, so gating appears to rely on local scripts (release:scripts) run as part of the release process rather than a hosted pipeline.
What Makes It Unique
The defining design choice is specializing the overwhelmingly common single-entry cache (maxSize: 1, the default) into its own code path that never touches the linked list at all, rather than treating every cache size uniformly through one general algorithm — most comparable libraries do not split behavior this way. Combined with fully opt-in expiration, stats, and event-emitter subsystems that add zero cost unless explicitly configured, this keeps the default, most-used configuration extremely lean while still supporting a comprehensive feature set (LRU, TTL, async-aware eviction, custom equality, key transforms) for use cases that need it. The repository’s own benchmark suite, pitting it against a dozen well-known alternatives across primitive/array/object and single/multi-argument shapes, is used to substantiate the performance claims rather than asserting them without evidence.
Used by 2 apps in this directory
Appsmith
Developer Tools · Automation · No Code Platforms
Open-source low-code platform to build admin panels, dashboards, and internal tools connected to any database or API.
Grafana
Monitoring · Analytics
The open-source observability platform that unifies metrics, logs, and traces from any data source into dynamic, queryable dashboards.