Memoization in JavaScript
Diego Palacios Lepore
Posted on April 24, 2024
Following my previous post about closures in JavaScript, I wanted to continue the conversation and talk about memoization, which is a performance optimization technique that leverages closures.
Memoization relies on function purity. Pure functions consistently return the same output for identical input arguments. Memoization enhances efficiency by caching these arguments along with the output they generate. When these arguments are passed again, the function retrieves the result from the cache instead of recalculating it. This is basically why memoization is ideal only on pure functions, but this will be clearer as I illustrate the concept in the following example.
Consider a basic function to calculate the sum of numbers:
function sumNums(...nums) {
return nums.reduce((total, num) => total + num, 0);
}
console.log(sumNums(1, 2, 3)); // Output: 6
Now, imagine that instead of a straightforward operation like summing three numbers, we're dealing with a more complex function that uses recursion and challenging computations. In such cases, memoization is particularly valuable.
In our example, memoization optimizes the function by ensuring that the arithmetic operation is performed just once. This is achieved by storing 1, 2, 3
as the key
and 6
as the value
in an object that acts as the function’s cache. In my demonstration, I'll use an object for the cache, but you can choose any data structure that best suits your specific needs and preferences.
Let's define a memo function that takes another function and memoizes it, and then let's memoize our sumNums
function:
/**
* Memoizes a function to cache its results based on the arguments provided.
* If the result for a given set of arguments is already in the cache, it retrieves it from there.
* Otherwise, it calculates the result, stores it in the cache, and returns it.
*
* @param {Function} fn - The function to be memoize.
* @returns {Function} The memoize function.
*/
function memo(fn) {
const cache = {};
return (...args) => {
// Generate a unique key based on arguments to store function results
const key = JSON.stringify(args);
// Check if the result is already cached
if (cache[key]) {
return cache[key];
}
// Calculate the result and cache it if it's not already cached
const result = fn(...args);
cache[key] = result;
return result;
};
}
// Create a memoize version of the sumNums function
const memoizedSumNums = memo(sumNums);
console.log(memoizedSumNums(1, 2, 3));
// Calculates and returns 6
console.log(memoizedSumNums(1, 2, 3));
// Fetches and returns 6 from cache without recalculating
I encourage you to try it out by memoizing different functions. For instance, you could try memoizing a fibbonacci function. And then you can compare how fast it runs with and without memoizing it.
To wrap up, memoization is a powerful technique for boosting the efficiency of pure functions by avoiding unnecessary recalculations, and enhancing performance. However, while memoization can significantly cut down on processing time, especially in scenarios involving complex tasks, it does so at the cost of increased memory usage.
As results are cached, memory consumption can grow, depending on the number and size of the cached results. This trade-off needs careful consideration, particularly in environments where memory is limited.
Opt for memoization when the benefits of reduced computation outweigh the costs of additional memory usage. This way, your functions not only run faster but also remain efficient in their use of system resources, keeping your code optimized and maintainable.
Posted on April 24, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.