All 15 Functional Interfaces & Method References questions
More in Functional Interfaces & Method References
Advertisement
Java › Functional Interfaces & Method References

How would you implement memoization (caching function results) as a higher-order function wrapping an existing Function, and what thread-safety considerations apply for a memoized function used concurrently?

Advanced

Answer

A memoizing wrapper takes an existing Function<T,R> and returns a new Function<T,R> that first checks an internal cache (typically a Map<T,R>) for a previously-computed result before falling back to invoking the original function and storing the result for future calls with the same input -- for thread-safe memoization under concurrent access, ConcurrentHashMap's computeIfAbsent() provides atomic check-then-compute semantics (avoiding a race where two threads redundantly compute the same expensive value simultaneously, and importantly avoiding the more subtle bug where the computation function itself recursively calls back into the same map during computeIfAbsent, which can cause a ConcurrentModificationException-like deadlock in certain JDK versions).
public static <T, R> Function<T, R> memoize(Function<T, R> function) {
    Map<T, R> cache = new ConcurrentHashMap<>();
    return input -> cache.computeIfAbsent(input, function);
}

Function<Integer, Long> slowFactorial = n -> { /* expensive computation */ return computeFactorial(n); };
Function<Integer, Long> fastFactorial = memoize(slowFactorial);
fastFactorial.apply(20);  // computed and cached
fastFactorial.apply(20);  // returned instantly from cache on second call
Real-world example An expensive recursive Fibonacci calculation function is wrapped with a generic memoize() utility, dramatically speeding up repeated calls with the same input across a request-handling application, using ConcurrentHashMap internally to remain safe under concurrent access from multiple request-handling threads.

Common follow-ups: What's the memory growth risk of an unbounded memoization cache, and how would you add eviction (like an LRU policy)?;Why can computeIfAbsent() with a recursive function cause issues in some JDK versions, and how would you avoid that pitfall?