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!