Type a memoize wrapper that keeps the wrapped function's arguments and result
memoize takes a pure function and returns a cached version of it.
Requirements:
type — both checked at every call site, with no any anywhere in the caller-facing signature;
- the returned function must accept the wrapped function's parameters and return its result
- a repeated call with the same arguments must not run the wrapped function again;
- deriving the cache key by serializing the arguments is acceptable.
export function memoize(fn) {
// your code here
}
Write the implementation.
Be generic over the function type and rebuild the signature from it: (...args: Parameters<T>) => ReturnType<T>. Parameters<T> extracts the argument tuple, ReturnType<T> the result, so the cached version is checked exactly like the original.
- ✗Returning
(...args: any[]) => any, which drops every check the original had - ✗Typing the wrapped function as
Function, soParameters<T>cannot be applied - ✗Forgetting that the cache key must be derived from the arguments, not from
T
- →How does
ReturnType<T>extract the result withinfer? - →What breaks if the wrapped function is overloaded?
The wrapper is generic over the function type, and its signature is rebuilt from Parameters<T> and ReturnType<T> — that is precisely what these utility types are for.
export function memoize<T extends (...args: any[]) => unknown>(
fn: T,
): (...args: Parameters<T>) => ReturnType<T> {
const cache = new Map<string, ReturnType<T>>();
return (...args: Parameters<T>): ReturnType<T> => {
const key = JSON.stringify(args);
if (!cache.has(key)) {
cache.set(key, fn(...args) as ReturnType<T>);
}
return cache.get(key) as ReturnType<T>;
};
}
const area = memoize((w: number, h: number): number => w * h);
area(2, 3); // number
area('2', 3); // Error: 'string' is not assignable to 'number'
Parameters<T> yields the argument tuple, ReturnType<T> the result type. Both are conditional types built with infer over T's signature, so the caller keeps the arity, the argument types and the result type. Return (...args: any[]) => any instead and area('2', 3) compiles in silence.