Lazy vs Eager: Closure Decides When Work Happens
The same closure can defer expensive work until it's actually needed, or do it once up front and cache the result. Neither is automatically better.
Closure doesn't just decide what a function remembers. It also decides when work actually happens, and that choice matters more than it looks.
A Function That Builds a Divider Line
Say you're formatting receipts, and you need a repeated dashed line at a given width:
function makeDivider(width) {
return function divider() {
return '-'.repeat(width);
};
}
const divider40 = makeDivider(40);
divider40(); // the string gets built right here
divider40(); // built again, from scratch, every callmakeDivider(40) closes over width. The actual string, the real work, only happens the moment divider40() is called, not before. Call it five times, and the string gets rebuilt five times.
This is called lazy, or deferred, execution. The work waits until the function actually runs.
The Alternative: Do the Work Once, Upfront
function makeDivider(width) {
const line = '-'.repeat(width);
return function divider() {
return line;
};
}Same closure, same width, but now the string gets built the moment makeDivider runs, not when divider() is called later. Every call to divider() just hands back the already-built value.
This is eager execution. The work happens immediately, once, and gets reused.
Which One Actually Wins
Wins when the function might never get called. Skipping the work entirely beats doing it and throwing it away.
Wins when the function definitely gets called, often. Pay the cost once, reuse it forever after.
Neither one is the correct default. The real question is simple: how likely is this to be called, and how many times? Rarely called, maybe never: defer it. Called constantly once it starts: do it once and cache it.
Both Are Only Possible Because of Closure
Notice what made both versions work at all: closure. The lazy version closes over width and waits. The eager version closes over line, an already-computed value, and just remembers it.
Choosing lazy versus eager is really just choosing which variable you hand to the closure: the raw input, or the finished result. Same mechanism, same function shape, completely different performance behavior depending on which one you pick.
That eager version, computing something once and handing back the cached answer forever after, is worth a second look on its own. It's the seed of a much more general technique, and it's next.
Keep reading