Memoization in JavaScript – When Optimization Goes Wrong

Introduction
Memoization is a widely praised optimization technique in JavaScript, often used to improve performance by caching the results of expensive function calls. However, in some cases, memoization can actually have the opposite effect. This article will walk through how memoization works, when it can slow down your code, and when you should use it.
Memoization Basics
Memoization works by storing the results of function calls so that if the same inputs are encountered again, the cached result is returned instead of recalculating. Let's start with a basic example—calculating a number raised to a power:
function calc(num, power) {
console.log('calculating...');
let result = num;
let i = 0;
while (i < power) {
result = result * num;
i++;
}
return result;
}
console.log(calc(2, 3)); // calculating... 8
console.log(calc(2, 3)); // calculating... 8
console.log(calc(2, 3)); // calculating... 8
Note: Not how you should calculate a power in JavaScript, but will work for demonstration purposes.
Every time we call calc(2, 3)
, we repeat the same calculation, even though the inputs are the same. This is where memoization can help.
Introducing Memoization
Now, let's optimize this function using memoization. We'll store the results of previously computed values in a cache to avoid repeating the same work:
function memoize(fn) {
const cache = {};
return function (...args) {
const key = JSON.stringify(args);
if (cache[key]) {
console.log('Returning memoized result');
return cache[key]; // Returning memoized result
}
console.log('Calculating new result');
const result = fn(...args); // Calculating new result
cache[key] = result; // Caching calculated result
return result; // Returning calculated result
};
}
const memoizedCalc = memoize(calc);
At first glance, this looks great—our repeated function calls return the cached result, and we avoid redundant calculations. This cache is saved using a closure.
Performance improvements
Simple operations
What happens when memoization is applied to a simple operation like raising a number to a small power, say 3? Let's compare the performance:
console.time('With Memoization');
memoizedCalc(2, 3);
memoizedCalc(2, 3);
memoizedCalc(2, 3);
console.timeEnd('With Memoization');
console.time('Without Memoization');
calc(2, 3);
calc(2, 3);
calc(2, 3);
console.timeEnd('Without Memoization');
Console output:

Performance Results:
The memoized version took 0.86ms
to execute, while the non-memoized version took 0.32ms
.
Surprisingly, memoization is slower - 169%
slower!
While memoization avoids recalculating the result, it also adds the extra step of managing the cache—storing, retrieving, and serializing the arguments. For lightweight operations like this, the overhead of maintaining the cache can outweigh the benefits of skipping recalculations.
Expensive Operations
Now, let's explore a more complex example, like raising a number to a power of 1,000,000. For this more expensive calculation, memoization can make a significant difference:
console.time('With Memoization');
memoizedCalc(2, 1000000);
memoizedCalc(2, 1000000);
memoizedCalc(2, 1000000);
console.timeEnd('With Memoization');
console.time('Without Memoization');
calc(2, 1000000);
calc(2, 1000000);
calc(2, 1000000);
console.timeEnd('Without Memoization');
Console output:

Performance Results:
The memoized version took 5.90ms
, while the non-memoized version took 20.49ms
. This means that memoization improved the retrieval speed by about 71%
.
In this case, memoization dramatically reduces the time needed for repeated calls, because the computation is far more expensive, and the cache retrieval is much faster in comparison.
Conclusion
Memoization can be a powerful optimization tool when used for expensive and repeated computations. However, as we've seen, it can also slow down your code when applied to lightweight functions, due to the overhead of managing the cache.
Key takeaway: Optimization techniques like memoization should be used carefully. Always evaluate whether the optimization adds value or creates unnecessary complexity. In some cases, "optimizing" may introduce new inefficiencies, so it's essential to profile and test your code for the best results.