memoize-one review
memoize-one 6.0.0 remembers exactly one successful invocation: the last argument list, its `this` reference, and the returned value. A repeat call hits only when the argument count and each value match the previous call; `NaN` receives a special equality case. Any different input replaces the entry, so `A, B, A` computes three times. Version 6 added `clear()` and corrected TypeScript declarations for the wrapper and custom comparators. The package has no TTL, key function, multi-entry storage, weak references, metrics, or built-in promise rejection policy.
memoize-one 6.0.0 installed in 0.3 seconds as one 1 MB package, added 0 dependencies, bundled types, and produced a 0.4 KB gzipped browser build in our sandbox. Install it only when the next call usually repeats the previous input; alternating keys or rejected promises need a cache with a different policy.
We installed it
| Install | ✓ · 0.3s | 1 package on disk · 1 MB |
| Import | ✓ | ESM import works · require() works · CommonJS package |
| Browser | 0.4 KB | gzipped (0.6 KB minified), bundled with esbuild |
| Types | ✓ | TypeScript types bundled |
| Known vulns | 0 | 0 critical · 0 high · 0 moderate · 0 low (npm audit) |
Answers from our run
Does memoize-one install cleanly?
Yes. In a fresh container with an empty cache, npm install memoize-one finished in 0.3s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.
How much does memoize-one add to a browser bundle?
0.4 KB gzipped (0.6 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.
Does memoize-one work with both ESM and CommonJS?
Yes. Both import 'memoize-one' and require('memoize-one') worked in Node 22 in our run. The package is published as CommonJS.
Does memoize-one include TypeScript types?
Yes, type declarations ship inside the package, so no @types install is needed.
memoize-one or memoizee: which should you use?
memoizee: Use it when calls need multiple cached keys, expiry, primitive-key handling, or promise-aware options. memoize-one 6.0.0 installed in 0.3 seconds as one 1 MB package, added 0 dependencies, bundled types, and produced a 0.4 KB gzipped browser build in our sandbox.
When should you not use memoize-one?
Hot calls alternate among two or more keys. Each change evicts the last result, so an A, B, A sequence never hits.
Use it if
- The same derived value is commonly requested on consecutive calls with stable object references.
- A child component or downstream equality check needs the previous array or object identity reused for unchanged inputs.
- Code outside a React function component needs the last-call behavior of `useMemo` without retaining a growing key set.
- A one-entry ceiling and explicit `clear()` are preferable to configuring expiry and eviction for a general cache.
- Hot calls alternate among two or more keys. Each change evicts the last result, so an `A, B, A` sequence never hits.
- Callers rebuild equivalent arrays or objects on every pass. Default comparison uses reference equality, and fresh containers force recomputation.
- The function returns a promise that can reject. The promise is cached as soon as it is returned, and its rejection persists until inputs change or `clear()` runs.
- You need expiry, several retained keys, weak-key collection, cache statistics, or automatic rejected-promise eviction. None is part of this package.
- Recent upstream releases and runtime testing are mandatory. Version 6.0.0 dates to October 2021, and the repository's last push was January 2023.
Setup reality
We installed memoize-one 6.0.0 in a clean Node 22 Bookworm container with no cache. npm finished in 0.3 seconds, and the single installed package occupied 1 MB. It has zero direct and zero peer dependencies, with 84 KB unpacked and an MIT license. npm audit found zero known vulnerabilities. Bundled TypeScript declarations were present.
The package is CommonJS and publishes no exports map, although both require() and ESM import succeeded on our Node 22 box. An esbuild import of the whole package produced 0.6 KB minified and 0.4 KB gzipped for the browser. Use the default import shown in the README. More important, allocate the memoized wrapper once. Creating it inside the repeated operation discards the saved call every time; module scope shares one entry among callers, while a class instance field keeps one per instance.
Default comparison uses strict equality for each argument, apart from treating NaN as equal to NaN, and a changed this reference always misses. A custom equality function receives two complete argument arrays. Deep comparison can cost more than the calculation being skipped, so compare only fields that affect the result. Version 6's EqualityFn<typeof fn> preserves the original parameter tuple and catches a comparator with the wrong shape.
A synchronous throw does not overwrite the previous success because caching happens after the function returns. A promise is already a returned value, so a later rejection remains the cached result for matching inputs. Clear it in a rejection path or use an async-aware package. clear() releases the stored arguments, receiver, and result. The wrapper adds that method but does not copy displayName, other custom properties, or the original function's length, which can break code that inspects callable metadata.
Patterns
Reuse the immediately repeated result memoize-last-call
import memoizeOne from 'memoize-one'
const sortRows = memoizeOne((rows, field) =>
[...rows].sort((left, right) =>
String(left[field]).localeCompare(String(right[field]))
)
)
const first = sortRows(rows, 'name')
const second = sortRows(rows, 'name')
console.log(first === second) // trueThe array reference and sort field must both match the last call. A new array with identical rows is still a miss.
Demonstrate one-entry replacement observe-single-entry-eviction
const computeOnce = memoizeOne(compute)
computeOnce('a') // miss
computeOnce('b') // miss and replaces a
computeOnce('a') // miss because b was latestOnly one invocation survives. Choose a multi-entry cache when several argument combinations alternate in normal traffic.
Keep a cache per mounted instance memoize-class-instance
class Results extends React.Component {
selectRows = memoizeOne((rows, query) =>
rows.filter((row) => row.name.includes(query))
)
render() {
const visible = this.selectRows(this.props.rows, this.props.query)
return <Table rows={visible} />
}
}An instance field prevents two mounted components from continually replacing one shared module-level entry.
Compare only result-bearing fields compare-object-fields
const buildFormatter = memoizeOne(
(options) => new Intl.NumberFormat(options.locale, {
style: 'currency',
currency: options.currency,
}),
([next], [previous]) =>
next.locale === previous.locale &&
next.currency === previous.currency
)The custom comparator receives argument tuples, and every comparison adds work before a possible cache hit.
Type a custom comparator type-custom-comparator
import memoizeOne, { type EqualityFn } from 'memoize-one'
function total(price: number, quantity: number) {
return price * quantity
}
const sameInputs: EqualityFn<typeof total> = (next, previous) =>
next[0] === previous[0] && next[1] === previous[1]
const memoizedTotal = memoizeOne(total, sameInputs)In version 6, `EqualityFn<typeof total>` derives the exact argument tuple from the wrapped function.
Drop the retained call clear-cached-result
const createIndex = memoizeOne(buildSearchIndex)
const index = createIndex(records)
createIndex.clear()
const rebuilt = createIndex(records)
console.log(index === rebuilt) // falseAfter `clear()`, the same inputs execute the original function and create a new cached result.
Evict a failed async request clear-rejected-promise
const requestUser = memoizeOne(fetchUser)
async function loadUser(id) {
try {
return await requestUser(id)
} catch (error) {
requestUser.clear()
throw error
}
}A returned promise is cached immediately. Clear after rejection or the same id receives that rejected promise again.
Include the receiver in cache identity preserve-this-binding
function readRate(code) {
return this.rates[code]
}
const memoizedRate = memoizeOne(readRate)
const usd = memoizedRate.call(rateBook, 'USD')
const usdAgain = memoizedRate.call(rateBook, 'USD')A different `this` reference causes a miss before any custom argument comparator runs.
Preserve the last success across a throw retain-good-result-after-throw
const parseConfig = memoizeOne((text) => {
if (!text) throw new Error('empty config')
return JSON.parse(text)
})
const valid = parseConfig('{"debug":true}')
try { parseConfig('') } catch {}
const sameValid = parseConfig('{"debug":true}')
console.log(valid === sameValid) // trueA synchronous exception is never stored, so it does not replace the preceding successful invocation.
Stabilize a derived prop object stabilize-derived-reference
const makeChartOptions = memoizeOne((theme, locale) => ({
color: theme.accent,
locale,
}))
const options = makeChartOptions(theme, locale)
return <MemoizedChart options={options} />The child receives the same object only while both `theme` and `locale` match the immediately previous call.
Require the CommonJS build import-commonjs
const memoizeOne = require('memoize-one')
const upper = memoizeOne((value) => value.toUpperCase())With no exports map, Node follows the package's `main` field to its CommonJS file.
Do not depend on copied function metadata avoid-function-metadata-assumption
function combine(left, right) {
return left + right
}
combine.displayName = 'combine'
const cachedCombine = memoizeOne(combine)
console.log(cachedCombine.length) // 0
console.log(cachedCombine.displayName) // undefinedThe callable wrapper adds `clear()` but does not inherit `length`, `displayName`, or other custom properties.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| memoizee | npm | Use it when calls need multiple cached keys, expiry, primitive-key handling, or promise-aware options. |
| quick-lru | npm | Use it when you want to manage a bounded set of key-value entries directly instead of wrapping one function. |
| lru-cache | npm | Use it for a configurable LRU with size limits, TTL behavior, disposal hooks, and fetch support. |
| fast-memoize | npm | Use it when multi-key function memoization and serializer choices fit better than one-call retention. |
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.

