Library

Function Memoization

Wrap a function and reuse its result for the same inputs. The next call with the same values skips the work.

JAVASCRIPT Free to use
Code
function memoize(fn, keyOf = defaultKey) {
    const cache = new Map();

    function wrapped(...args) {
        const key = keyOf(...args);

        if (cache.has(key)) {
            return cache.get(key);
        }

        const result = fn.apply(this, args);
        cache.set(key, result);

        if (isThenable(result)) {
            Promise.resolve(result).catch(function () {
                cache.delete(key);
            });
        }

        return result;
    }

    wrapped.clear = function () {
        cache.clear();
    };

    return wrapped;
}

function defaultKey(...args) {
    return args.map(function (value) {
        if (value === null) {
            return 'null';
        }

        const type = typeof value;
        if (type === 'string') {
            return 's:' + value;
        }
        if (type === 'number') {
            return 'n:' + String(value);
        }
        if (type === 'boolean') {
            return 'b:' + String(value);
        }
        if (type === 'bigint') {
            return 'i:' + String(value);
        }
        if (type === 'undefined') {
            return 'undefined';
        }
        if (type === 'object') {
            return 'o:' + JSON.stringify(value);
        }

        return type + ':' + String(value);
    }).join('|');
}

function isThenable(value) {
    return value != null && typeof value.then === 'function';
}

function memoizeWeak(fn) {
    const cache = new WeakMap();

    return function (object, ...rest) {
        if (object == null || typeof object !== 'object') {
            throw new TypeError('First argument must be an object');
        }

        let inner = cache.get(object);
        if (!inner) {
            inner = new Map();
            cache.set(object, inner);
        }

        const key = defaultKey(...rest);
        if (inner.has(key)) {
            return inner.get(key);
        }

        const result = fn.apply(this, [object, ...rest]);
        inner.set(key, result);
        return result;
    };
}
Quick try
const fibonacci = memoize(function fibonacci(n) {
    if (n <= 1) {
        return n;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
});

console.log(fibonacci(40));
console.log(fibonacci(40)); // from cache
fibonacci.clear();

const area = memoize(function (width, height) {
    return width * height;
});

console.log(area(4, 5));
console.log(area('4', 5)); // different key from 4, 5

What it does

  • A later call with the same inputs returns the stored value and skips the work.
  • If an async function fails, that key is removed so the next call can retry.
  • The default key treats 1 and "1" as different. For circular objects, pass your own keyOf.

Where it fits

A practical place for this snippet in a real project.

Useful for a repeated calculation with the same inputs, or a request that runs more than once in the same session. It is not a store for data that must stay fresh.

Similar snippets

Binary Search Tree
JAVASCRIPT

Binary Search Tree

Insert, search, and delete while keeping values sorted. Average O(log n), worst O(n) if the tree becomes a chain.

LRU Cache
JAVASCRIPT

LRU Cache

Keep a fixed number of entries and evict the least recently used item when the cache is full.

Breadth and Depth Traversal
JAVASCRIPT

Breadth and Depth Traversal

An undirected graph: visit in breadth or depth, find the shortest hop path, and detect a cycle without counting the walk back to a parent.

Need a snippet that is not here?

Describe the problem or idea, and I will see if it belongs in the library.