Now and then, JavaScript quietly teaches me something, sitting under the surface. Recently, that lesson came from a tiny custom iterator I was writing just out of curiosity.
I wasn't trying to solve anything deep.
But while experimenting, I bumped into a pattern that shows up in so many places:
Where you put your closure state determines whether your function/object becomes reusable or single-use.
It's a deeper concept that affects half the JavaScript patterns we write without even thinking.
Let me show you how I discovered it.
Example 1: A Custom range() Iterator — Not Wrong, Just Interesting
Here's the custom range() iterator I wrote:
function range(from, to) {
let nextIndex = from; // this state lives in the closure of range()
return {
next() {
if (nextIndex <= to) {
return { value: nextIndex++, done: false };
}
return { done: true };
},
[Symbol.iterator]() {
return this; // this object IS the iterator
},
};
}
Using it:
const r = range(1, 4);
for (let x of r) console.log(x);
// -> 1 2 3 4
for (let x of r) console.log(x);
// -> (nothing)
And here's the thing:
- This is not a bug.
- This is exactly how generator functions behave.
Generators are intentionally single-use.
Once their internal state moves forward, it stays moved.
My custom iterator behaves the same way.
So nothing is "wrong".
But this led me to a deeper realization:
The single-use behavior has nothing to do with iterators being tricky.
It's simply because:
- The state (nextIndex) lives in the outer closure
- And [Symbol.iterator] returns the same object every time
Which means the iterator cannot restart - the internal state never resets.
This is where the real pattern shows up.
If You Want a Reusable Iterable…
…you only need to change where the state lives:
function range(from, to) {
return {
[Symbol.iterator]() {
let nextIndex = from; // fresh state per iterator instance
return {
next() {
if (nextIndex <= to) {
return { value: nextIndex++, done: false };
}
return { done: true };
},
};
},
};
}
Now:
for (let x of r) console.log(x); // 1 2 3 4
for (let x of r) console.log(x); // 1 2 3 4 (restarts)
Fresh iterator -> fresh nextIndex -> reusable iterable.
And that's when I realized:
This isn't about iterators.
It's about closure state and how we structure our functions.
To test the theory, I tried something completely different - a pipe() function.
And it exposed the same pattern.
Example 2: A Pipe Function That Accidentally Became Single-Use
If you haven't used pipe() before:
It's a small functional helper that takes a list of functions and runs them in sequence:
pipe([f1, f2, f3])(value)
is basically:
f3(f2(f1(value)))
Here's the first version I wrote:
function pipe(funcs) {
let i = 0; // state stored OUTSIDE merged()
return function merged(...args) {
if (funcs.length === 0) return args[0];
const out = funcs[i](...args);
if (i === funcs.length - 1) {
return out;
}
i++;
return merged(out);
};
}
Usage:
const fn = pipe([x => x * 2, x => x + 3]);
console.log(fn(5)); // 13
console.log(fn(5)); // 8 (state leaked across calls)
And then I laughed because this is the same pattern as the iterator example:
- 'i' lives in the outer closure
- The returned function shares that 'i' across invocations
- Which means the whole pipeline is single-use
Exactly like the iterator.
Fixing It (Again) By Moving State Inside
function pipe(funcs) {
return function merged(...args) {
function helper(i, val) {
if (i >= funcs.length) return val;
return helper(i + 1, funcs[i](val));
}
return helper(0, ...args); // fresh state per call
};
}
Now:
fn(5); // 13
fn(5); // 13
The Actual Pattern
After seeing this in both examples, the pattern became very clear:
If your state lives in the outer closure, you create a single-use function/object.
If your state is created inside a nested function or factory, you get a reusable function/object.
This applies to:
- iterators
- generators
- pipes
- debounce/throttle
- decorators
- memoization
- middleware
- event callback wrappers
Basically, half of JavaScript.
What This Taught Me
I used to look at nested functions and think:
"Why do people put functions inside functions? Just flatten it!"
Now I understand why:
- Nested functions give you fresh closure state
- Fresh state gives you reusable behavior
- Outer closure state gives you single-use behavior
What About You?
Have you ever written something that mysteriously worked only once?
Or something that behaved "weirdly" because of closure state placement?
I'd love to hear other examples - this pattern is everywhere once you learn to see it.
Top comments (5)
As you continue "evolving" this work (well done, btw), you end up rediscovering something like IxJS or something even more powerful and useful such as Observables. RxJS will open up a whole world of possibilities once you discover it, let alone if you combine it with Rimmel.js for the UI...
Wow, this was a super clear explanation — I really enjoyed how you broke down the closure pattern with simple examples. Great insight, and it actually made me rethink how I structure functions!
Some comments may only be visible to logged-in visitors. Sign in to view all comments.