I was solving LeetCode #2622 – Cache With Time Limit and the goal was to create a function that returns a caching system with TTL (time-to-live).

My first instinct? Use a closure. It's a common pattern — just define a variable outside the function and the inner function can reference it.

🔁 Closure-style cache:

function createCache() {
  const store = new Map();

  return {
    set(key, value, ttl) {
      store.set(key, { value, expiry: Date.now() + ttl });
    },
    get(key) {
      const cached = store.get(key);
      if (!cached || cached.expiry < Date.now()) return -1;
      return cached.value;
    }
  };
}

Easy. It works, it's scoped and no one can touch store from the outside — classic closure encapsulation. But the LeetCode problem expected us to extend a function via its prototype — and that threw me off. I was like: "How the heck do I create a private variable across prototype methods?"
That’s when the light bulb went on.

Using this + Classes = Encapsulation

I realized I needed to treat it like a class and that’s when I remembered: when using prototypes (or classes), you can store shared data inside this.
So I did:

var TimeLimitedCache = function () {
  this.cache = new Map();
};

TimeLimitedCache.prototype.set = function (key, value, duration) {
  const expiredAt = Date.now() + duration;
  const existed = this.cache.has(key);
  this.cache.set(key, { value, expiredAt });
  return existed;
};

TimeLimitedCache.prototype.get = function (key) {
  const entry = this.cache.get(key);
  if (!entry || entry.expiredAt < Date.now()) return -1;
  return entry.value;
};

Now everything lives inside this.cache. No need for an external closure and it works great across all methods.

Private Fields in Classes

At that point I thought: "Wait, so this whole thing is basically behaving like a class!". And if it’s a class, can we go one step further? What about private fields?
In the past, we'd use a naming convention like _cache, but that was just a signal, not actual protection.
But now with modern JavaScript (since ES2022), we can use native private fields with the # symbol:

class TimeLimitedCache {
  #cache = new Map();

  set(key, value, duration) {
    const expiredAt = Date.now() + duration;
    const existed = this.#cache.has(key);
    this.#cache.set(key, { value, expiredAt });
    return existed;
  }

  get(key) {
    const entry = this.#cache.get(key);
    if (!entry || entry.expiredAt < Date.now()) return -1;
    return entry.value;
  }
}

That’s real encapsulation!

What I Learned

  • Closures are a great way to encapsulate state, especially for factory functions;
  • Classes with this are a powerful alternative when you want to work with prototypes or build something that feels more like an object;
  • Modern JavaScript gives us native private fields (#);

No matter what pattern you choose, closures or classes, the key is to understand the trade-offs and when each tool fits better.

Solving this problem gave me a new perspective on how classes and closures can often solve the same thing in different ways.

Console You Later!