DEV Community

Matsu
Matsu

Posted on

Closures vs Classes in JavaScript: What I Learned Solving LeetCode #2622

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;
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)