FIELD NOTE 030TypeScriptReviewed July 2026

Generic Memoization Function

Caches function results based on arguments to avoid redundant computations

Copy the complete example or adapt its typed interface to your project. Comments are kept inside the snippet so the intent travels with the code.

pattern.ts
/**
 * Generic memoization function
 * @param fn Function to memoize
 * @returns Memoized function
 */
function memoize<T extends (...args: any[]) => any>(fn: T): T {
  const cache = new Map<string, ReturnType<T>>();
  
  return function(this: ThisParameterType<T>, ...args: Parameters<T>) {
    const key = JSON.stringify(args);
    
    if (cache.has(key)) {
      return cache.get(key);
    }
    
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  } as T;
}

// Usage Example
// Expensive computation function
const fibonacci = memoize((n: number): number => {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
});

console.log(fibonacci(10)); // Computed and cached
console.log(fibonacci(10)); // Returned from cache (much faster)

// With object arguments
const getUserData = memoize(async (userId: number, options: { details: boolean }) => {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  return response.json();
});

Perfect for optimizing expensive computations, API calls, or any function with repeated identical calls

Implementation note

Review the input assumptions and browser or runtime support before adopting a utility unchanged. Small helpers are easiest to maintain when their contract stays narrow.

How it works

The wrapper serializes arguments into a cache key, stores the computed return value in a Map, and returns that value for later calls with the same serialized input. The generic signature preserves the original callable shape.

What to watch for

Serialization has the same limits as JSON and object property order can affect keys. The cache grows without bounds, rejected promises can remain cached, mutable arguments can change after lookup, and side-effecting functions must never be memoized.

Production use

Use memoization for deterministic, expensive calculations with a small repeatable input space. Add eviction or a WeakMap strategy when lifetimes are unbounded, and define whether async failures, undefined results, or object identity should be cached.

Before you ship

  • Confirm the input contract with tests that cover empty, invalid, boundary, and representative real-world values before sharing the helper across a codebase.
  • Keep the function focused on one responsibility and name any project-specific policy explicitly instead of hiding it inside a generic-looking utility.
  • Measure behavior in the browsers or runtime versions your product supports, especially when timing, storage, DOM state, locale, or network access is involved.
  • Document the return type, mutation behavior, failure mode, and performance characteristics beside the call site so future maintainers can evaluate changes safely.
  • Compare the example with current official platform documentation when it relies on a browser API, language feature, or runtime behavior that may change over time.
  • If the helper crosses a trust boundary, validate again on the server and avoid treating browser-side checks, TypeScript annotations, or formatted output as a security guarantee.
  • Add a small regression test whenever a real edge case is discovered; concrete examples preserve the reason behind a guard more reliably than a general comment.