Skip to main content

memoize-one vs micro-memoize vs reselect: Memoization in JavaScript (2026)

·PkgPulse Team

TL;DR

memoize-one caches only the most recent call — perfect for React components where the same function is called repeatedly with the same recent arguments. micro-memoize is the most flexible and fastest general-purpose memoizer — configurable cache size, custom equality, and supports async functions. reselect is the Redux selector memoization library — composes input selectors, memoizes the result selector, and is used across the Redux ecosystem. For React component methods and derived values: memoize-one. For general function memoization with cache size control: micro-memoize. For Redux/Zustand derived state: reselect.

Key Takeaways

  • memoize-one: ~10M weekly downloads — 1-result cache, correct this context binding, React patterns
  • micro-memoize: ~5M weekly downloads — configurable N-result cache, async support, fastest library
  • reselect: ~8M weekly downloads — Redux selector memoization, composable, createSelector API
  • memoize-one clears cache on every new argument set — never leaks memory
  • micro-memoize's default cache size is 1 (like memoize-one) but configurable up to Infinity
  • useMemo (React) often replaces these in component-level code — libraries for other uses

The Memoization Problem

// Without memoization — recalculates every render even if input is the same:
function getFilteredPackages(packages: Package[], minScore: number) {
  return packages.filter((p) => p.healthScore >= minScore)
    .sort((a, b) => b.healthScore - a.healthScore)
}

// Called on every render — if packages and minScore haven't changed, wasted work:
const filtered = getFilteredPackages(allPackages, 80)

// With memoization — returns cached result if inputs are the same:
import { memoizeOne } from "memoize-one"

const memoizedFilter = memoizeOne(getFilteredPackages)
memoizedFilter(allPackages, 80)  // Runs filter
memoizedFilter(allPackages, 80)  // Returns cached result (same args)
memoizedFilter(allPackages, 70)  // Runs again (different minScore)
memoizedFilter(allPackages, 70)  // Returns cached result

memoize-one

memoize-one — single-result cache memoization:

Basic usage

import { memoizeOne } from "memoize-one"

// Memoize any function:
function formatPackageData(packages: Package[], locale: string) {
  console.log("Recalculating...")
  return packages.map((p) => ({
    ...p,
    formattedDownloads: new Intl.NumberFormat(locale).format(p.downloads),
  }))
}

const memoizedFormat = memoizeOne(formatPackageData)

const result1 = memoizedFormat(packages, "en-US")  // "Recalculating..."
const result2 = memoizedFormat(packages, "en-US")  // No log — cached!
const result3 = memoizedFormat(packages, "de-DE")  // "Recalculating..." (new locale)
const result4 = memoizedFormat(packages, "en-US")  // "Recalculating..." (prev was de-DE)

// memoize-one only caches the LAST call's result
// This makes it memory-safe — old results are GC'd

React class component method

import { memoizeOne } from "memoize-one"
import { Component } from "react"

interface Props {
  packages: Package[]
  minScore: number
}

class PackageList extends Component<Props> {
  // Memoize expensive filter — recalculates only when props change:
  getFilteredPackages = memoizeOne(
    (packages: Package[], minScore: number) =>
      packages
        .filter((p) => p.healthScore >= minScore)
        .sort((a, b) => b.healthScore - a.healthScore)
  )

  render() {
    const { packages, minScore } = this.props
    const filtered = this.getFilteredPackages(packages, minScore)

    return <ul>{filtered.map((p) => <PackageCard key={p.name} package={p} />)}</ul>
  }
}

// This pattern is equivalent to useMemo in function components:
// const filtered = useMemo(() => ..., [packages, minScore])

Custom equality check

import { memoizeOne } from "memoize-one"

// Default: strict equality (===)
// Custom: deep equality for object/array args:
import { isEqual } from "lodash"

const memoizedFn = memoizeOne(
  (filter: { minScore: number; tags: string[] }) =>
    computeExpensiveResult(filter),
  isEqual  // Use deep equality — { minScore: 80, tags: ["ui"] } matches again
)

// With deep equality:
memoizedFn({ minScore: 80, tags: ["ui"] })  // Computed
memoizedFn({ minScore: 80, tags: ["ui"] })  // Cached! (deep equal)
memoizedFn({ minScore: 80, tags: ["react"] })  // Recomputed

// Warning: isEqual is expensive — use it only when shallow comparison isn't enough

micro-memoize

micro-memoize — fast, configurable memoization:

Basic usage

import memoize from "micro-memoize"

// Default: cache size of 1 (same as memoize-one), strict equality:
const memoizedFn = memoize((a: number, b: number) => {
  console.log("Computing...")
  return a + b
})

memoizedFn(1, 2)  // "Computing..." → 3
memoizedFn(1, 2)  // Cached → 3
memoizedFn(2, 3)  // "Computing..." → 5
memoizedFn(1, 2)  // "Computing..." (cache evicted by 2,3)

Configurable cache size

import memoize from "micro-memoize"

// Cache last 10 calls (LRU-like eviction):
const memoizedFormatter = memoize(
  (value: number, currency: string) =>
    new Intl.NumberFormat("en-US", { style: "currency", currency }).format(value),
  { maxSize: 10 }
)

// Useful when function is called with a known set of arguments:
// e.g., 10 packages × currency — all cached:
memoizedFormatter(1999, "USD")  // "$19.99"
memoizedFormatter(1999, "EUR")  // "€19.99"
memoizedFormatter(2499, "USD")  // "$24.99"
// All 3 cached — different (value, currency) pairs

Async memoization

import memoize from "micro-memoize"

// Memoize async functions:
const fetchPackageData = memoize(
  async (packageName: string) => {
    console.log(`Fetching ${packageName}...`)
    const res = await fetch(`https://registry.npmjs.org/${packageName}`)
    return res.json()
  },
  {
    maxSize: 50,   // Cache 50 packages
    isPromise: true,  // Store the promise — parallel calls for same key share one promise
  }
)

// Multiple parallel calls for "react" — only ONE fetch:
const [data1, data2] = await Promise.all([
  fetchPackageData("react"),
  fetchPackageData("react"),  // Returns same promise as above
])
// Only one "Fetching react..." logged

Custom equality

import memoize from "micro-memoize"

// Custom equality for complex objects:
const memoizedChart = memoize(
  (config: ChartConfig) => generateChart(config),
  {
    isEqual: (a, b) =>
      a.type === b.type &&
      a.data.length === b.data.length &&
      a.data.every((v, i) => v === b.data[i]),
    maxSize: 5,
  }
)

reselect

reselect — Redux selector memoization:

Basic selector

import { createSelector } from "reselect"

// Input selectors (extract data from state):
const selectPackages = (state: RootState) => state.packages.items
const selectFilter = (state: RootState) => state.packages.filter

// Result selector (memoized — only recomputes when inputs change):
const selectFilteredPackages = createSelector(
  [selectPackages, selectFilter],
  (packages, filter) => {
    console.log("Recomputing filtered packages...")
    return packages
      .filter((p) => p.healthScore >= filter.minScore)
      .filter((p) => !filter.tags.length || filter.tags.some((t) => p.tags.includes(t)))
      .sort((a, b) => b.healthScore - a.healthScore)
  }
)

// Usage in component:
import { useSelector } from "react-redux"

function PackageList() {
  // Will only re-render if selectFilteredPackages returns a new reference:
  const packages = useSelector(selectFilteredPackages)
  return <ul>{packages.map((p) => <PackageCard key={p.name} package={p} />)}</ul>
}

Composing selectors

import { createSelector } from "reselect"

// Build complex selectors from simpler ones:
const selectAllPackages = (state: RootState) => state.packages.items
const selectSearchQuery = (state: RootState) => state.search.query
const selectSortField = (state: RootState) => state.ui.sortField

// Compose:
const selectSearchResults = createSelector(
  [selectAllPackages, selectSearchQuery],
  (packages, query) =>
    query ? packages.filter((p) => p.name.includes(query.toLowerCase())) : packages
)

const selectSortedSearchResults = createSelector(
  [selectSearchResults, selectSortField],
  (packages, sortField) => [...packages].sort((a, b) => b[sortField] - a[sortField])
)

// Each selector memoizes independently — reselect only recomputes when its
// specific inputs change:
// selectSearchResults recomputes when packages or query changes
// selectSortedSearchResults recomputes when searchResults or sortField changes

reselect v5 (2024) — improved TypeScript

import { createSelector } from "reselect"

// reselect v5 has much better TypeScript inference:
const selectActivePackages = createSelector(
  [(state: RootState) => state.packages, (state: RootState) => state.filter.minScore],
  (packages, minScore) => packages.filter((p) => p.healthScore >= minScore)
)
// Return type inferred as Package[] — no explicit types needed

Feature Comparison

Featurememoize-onemicro-memoizereselect
Cache size1 (fixed)Configurable1 (default)
Async support
Custom equality
Redux integration✅ Built for it
Selector composition
this bindingN/A
TypeScript✅ Excellent v5
Bundle size~1KB~2KB~5KB
ESM

When to Use Each

Choose memoize-one if:

  • React class component methods that recalculate on render
  • Any function where you only need to cache the most recent call
  • When memory safety is paramount — single cached result, no growth
  • Replacing useMemo in non-React code (Svelte, Vue, plain JS)

Choose micro-memoize if:

  • Need configurable cache size (cache N recent calls)
  • Async function memoization with promise deduplication
  • Multiple argument combinations need to be cached simultaneously
  • Fastest raw memoization performance

Choose reselect if:

  • Redux or Zustand derived state
  • Composing multiple input selectors into a computed value
  • Preventing unnecessary re-renders from state selection
  • RTK Query cache + selector composition

Use React's built-ins when in React:

// For component-level memoization, React's built-ins are usually sufficient:

// useMemo — memoize computed values:
const filtered = useMemo(
  () => packages.filter((p) => p.score >= minScore),
  [packages, minScore]
)

// useCallback — memoize function references:
const handleSelect = useCallback(
  (id: string) => dispatch(selectPackage(id)),
  [dispatch]
)

// React.memo — memoize component renders:
const PackageCard = React.memo(({ package: pkg }: { package: Package }) => (
  <div>{pkg.name}</div>
))

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on memoize-one v6.x, micro-memoize v4.x, and reselect v5.x.

Compare React and utility packages on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.