JavaScript Memoization — What does this output?
This is a daily Javascript challenge from the CodeShot archive. Practice your knowledge of Memoization Map Cache Pattern and improve your technical interview readiness.
const cache = new Map()
function expensive(n) {
if (cache.has(n)) return cache.get(n)
const result = n * n
cache.set(n, result)
return result
}
console.log(expensive(4))
console.log(expensive(4))
Detailed Explanation
Why This Question Matters
At first glance, this snippet looks like a "trick" question, but it's actually testing your understanding of Memoization.
Memoization is a fancy word for "remembering the result of a function call so we don't have to do the hard work again." In a real-world production environment, this is the difference between a page that loads in 100ms and one that hangs for 5 seconds because it's recalculating the same heavy dataset over and over.
Developers often trip up here because they either overthink the Map object or forget how closures and global scope affect state. If you've ever wondered why your React components are re-rendering too often or why your API calls are hitting the server more than they should, you're essentially dealing with the same problem this code solves.
Understanding the Code
Let's look at what's actually happening here.
First, we initialize a Map called cache. A Map is basically a dictionary—it stores key-value pairs. We put it outside the function so that its state persists across multiple calls. If it were inside the function, it would be wiped every time the function ran, making the whole thing pointless.
Now, let's trace the execution:
1. The first call expensive(4):
- The function checks cache.has(4). Since the map is empty, this is false.
- It skips the if block and calculates 4 * 4, which is 16.
- It saves that 16 into the map: cache.set(4, 16).
- It returns 16.
2. The second call expensive(4):
- The function checks cache.has(4). This time, it's true.
- It immediately hits return cache.get(4), which returns 16.
- The multiplication logic is skipped entirely.
Finding the Correct Answer
The output for both calls is 16 and 16.
If this were a multiple-choice question, Option B (16, 16) is the winner.
Why do people get this wrong? Some might think the Map doesn't persist, or they might expect the second call to return undefined because they confuse Map.get() with something else. Others might think the function "resets" itself.
But in JavaScript, as long as cache is in the outer scope, it stays alive. The first call populates the cache; the second call consumes it.
Common Mistakes Developers Make
One of the biggest pitfalls I see is using a plain JavaScript object {} instead of a Map. While an object works for simple keys, Map is generally better for caching because it handles non-string keys more efficiently and has a cleaner API (has, get, set).
Another common mistake is forgetting about memory leaks.
In this example, the cache grows forever. If you call expensive() with a million different numbers, that Map will hold a million entries in RAM. In a real application, a "naive" cache like this can crash your process. Senior devs usually implement a Least Recently Used (LRU) cache or use a WeakMap if the keys are objects, allowing the garbage collector to clean up unused data.
Lastly, some devs try to memoize functions that aren't "pure." If your function depends on a global variable or a database call that changes, caching the result will lead to stale data and bugs that are a nightmare to debug.
Real-World Usage
You see this pattern everywhere in modern engineering.
- React's useMemo and useCallback: These are essentially built-in memoization hooks. They prevent expensive calculations from running on every single render.
- API Gateways: Redis is basically a giant, distributed Map that lives on a server. Instead of hitting a slow database, the app checks Redis first.
- Recursive Algorithms: If you're solving a Fibonacci sequence or a complex tree traversal, memoization turns an exponential time complexity $O(2^n)$ into linear time $O(n)$.
For example, if you're building a dashboard that calculates a user's total spend across 10,000 transactions, you don't want to run that sum every time the user toggles a UI switch. You calculate it once, store it in a cache, and serve it instantly.
Key Takeaways
- Memoization is just caching the results of expensive function calls.
- Scope matters: The cache must live outside the function to be useful.
- Maps over Objects: Use Map for dynamic key-value pairs; it's more performant and explicit.
- Watch the memory: Be careful with unbounded caches in production; they can lead to memory leaks.
- Purity is key: Only memoize "pure" functions (same input always equals same output).
Why this matters
Understanding Memoization Map Cache Pattern is crucial for passing technical interviews. In real-world applications, this concept often leads to subtle bugs if not handled correctly. For more details, you can always refer to the official MDN Documentation.