Leaderboard
Javascript Jun 17, 2026
Javascript Memoize Falsy Cache Value Bug

JavaScript Closures — Spot the Memoization Bug!

This is a daily Javascript challenge from the CodeShot archive. Practice your knowledge of Memoize Falsy Cache Value Bug and improve your technical interview readiness.

function memoize(fn) {
  const cache = {}
  return function(n) {
    if (cache[n]) return cache[n]
    cache[n] = fn(n)
    return cache[n]
  }
}
A cache should be a Map not an object
B cache[n] is falsy when result is 0 or false — should check n in cache
C fn needs to be async
D The return is in the wrong place

Detailed Explanation

Why This Question Matters

Memoization is one of those concepts that sounds fancy in an interview but is actually just a glorified cache. At its core, it's about trading memory for speed. You store the result of an expensive function call so that if the same input comes back, you don't have to do the hard work again.

The problem is that implementing a *correct* memoization wrapper is trickier than it looks. Most developers can write a basic version, but they often miss the edge cases—specifically how JavaScript handles "falsy" values. This is where a simple bug can turn a performance optimization into a production nightmare.

Understanding the Code

Let's look at the snippet provided:

function memoize(fn) {
  const cache = {}
  return function(n) {
    if (cache[n]) return cache[n]
    cache[n] = fn(n)
    return cache[n]
  }
}

Here is what's happening under the hood:
1. We create a cache object. Because of closures, the returned function maintains access to this object even after memoize has finished executing.
2. When the returned function is called with an argument n, it checks if cache[n] exists.
3. If it does, it returns the cached value.
4. If not, it runs the original function fn(n), stores the result, and returns it.

On the surface, this looks perfect. If you're calculating Fibonacci numbers or processing a huge dataset, this should save you a ton of CPU cycles. But there is a glaring flaw in the logic.

Finding the Correct Answer

The bug lies in this line: if (cache[n]).

In JavaScript, if (value) doesn't check if a value *exists*; it checks if the value is truthy.

Imagine your expensive function fn(n) returns 0, false, null, or an empty string "". The first time you call the function with a specific input, it calculates the result (say, 0) and stores it in the cache: cache[n] = 0.

The next time you call the function with that same input, the code hits if (cache[n]). Since 0 is falsy, the if statement evaluates to false. The code ignores the cached value and runs the expensive calculation all over again.

The fix? You need to explicitly check if the key exists in the object, regardless of whether the value is truthy or falsy.

The correct approach (Option B) would look like this:

if (n in cache) return cache[n];
Or, using hasOwnProperty:
if (Object.prototype.hasOwnProperty.call(cache, n)) return cache[n];

By using the in operator or hasOwnProperty, we are asking "Does this key exist?" rather than "Is the value stored at this key truthy?"

Common Mistakes Developers Make

This is a classic "junior-to-mid" mistake. We get used to the shorthand if (variable) for checking if something is there, but that habit fails us when the valid return value of a function is 0 or false.

Another common pitfall is key collision. In the example above, the cache is a plain object {}. JavaScript object keys are converted to strings. If your fn takes an object or an array as an argument, cache[n] will turn that object into the string "[object Object]". If you call the function with two different objects, they will both map to the same cache key, and you'll get the wrong result for the second call.

For a truly robust memoize function, you'd want to use a Map, which allows any type of value (including objects) as a key.

Real-World Usage

You'll see this pattern everywhere in modern frontend development. React's useMemo and memo are essentially built-in versions of this concept. They prevent components from re-rendering or recalculating values unless the dependencies actually change.

In backend engineering, this is the foundation of caching layers. Whether you're using an in-memory cache or something like Redis, the logic is the same: check if the key exists, return it if it does, otherwise compute and store.

However, in production, you can't just let a cache object grow forever. That's a memory leak waiting to happen. Real-world memoization usually involves a cache eviction policy, like Least Recently Used (LRU), where the oldest entries are deleted once the cache reaches a certain size.

Key Takeaways

- Truthy $\neq$ Existence: Never use if (value) to check if a value exists in a cache if 0, false, or "" are valid results.
- Use in or Map: Use the in operator or a Map object to verify if a key has been set.
- Mind the Keys: Remember that plain JS objects stringify their keys. If your inputs aren't strings or numbers, use a Map.
- Watch Your Memory: In a real app, an unbounded cache will eventually crash your process. Always consider how you'll clear old data.

Why this matters

Understanding Memoize Falsy Cache Value Bug 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.

📝
Reviewed by CodeShot Editorial
Every challenge is code-reviewed by senior developers to ensure accuracy and real-world relevance. Learn more.

Ready for your shot?

Join thousands of developers solving one logic puzzle every morning.

Solve Today's Challenge →