mrkeyoor.com_
Sun 20 Sept 07:02 UTC
npmUtilsupdated 20 Sept 2026

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.

27.0Mdownloads / wk
Verdict

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

Lab card: what happened when we installed memoize-oneScreenshot of memoize-one documentation
Install✓ · 0.3s1 package on disk · 1 MB
ImportESM import works · require() works · CommonJS package
Browser0.4 KBgzipped (0.6 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 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.

API stability5/5The public surface remains one default function with an optional equality callback. Version 6 added `clear()` and narrowed declarations without changing how the wrapper executes, compares, or retains its single result. The release notes explicitly describe the major bump as a TypeScript compatibility signal: code that assumed copied function properties or used an incorrectly shaped comparator may fail to compile, while normal calls keep the same behavior.
Docs4/5The README explains the one-entry replacement rule with an `A, B, A` example, strict argument equality, the `NaN` exception, complete argument arrays in custom comparators, receiver handling, synchronous throws, `clear()`, and missing function metadata. That is enough to predict most hits and misses. There is no separate versioned manual, and its benchmark section refers to older Node environments, so it should not be used as current performance evidence.
Maintenance2/5The repository is not archived and GitHub lists only 17 open issues and pull requests, but it was last pushed on January 8, 2023. The newest npm release, 6.0.0, shipped on October 20, 2021. Its tiny zero-dependency implementation reduces exposure to ecosystem churn, and our Node 22 checks all passed, yet teams requiring current upstream responses or fresh CI coverage have no recent release activity to rely on.
Ecosystem4/5npm counted 37,249,453 downloads for the week ending August 24, 2026, and GitHub reports 2,967 stars. CommonJS require, ESM import, bundled TypeScript declarations, and a 0.4 KB gzipped browser result cover most JavaScript build setups. The project intentionally has no plugins or policy knobs; Redux selectors, asynchronous calls, and several retained argument sets belong to other packages.

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.
Skip it if

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) // true

The 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 latest

Only 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) // false

After `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) // true

A 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) // undefined

The callable wrapper adds `clear()` but does not inherit `length`, `displayName`, or other custom properties.

Alternatives

PackageRegistryPick it when
memoizeenpmUse it when calls need multiple cached keys, expiry, primitive-key handling, or promise-aware options.
quick-lrunpmUse it when you want to manage a bounded set of key-value entries directly instead of wrapping one function.
lru-cachenpmUse it for a configurable LRU with size limits, TTL behavior, disposal hooks, and fetch support.
fast-memoizenpmUse 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.