Implement cycle(...values) — given several values, return a function that hands back the next value every time you call it, looping forever once it runs off the end. Think of a playlist set to repeat: track 1, track 2, track 3, then back to track 1. The values you pass are fixed; the position advances on each call and wraps around. Each call to cycle(...) produces its own independent stepper — calling one never affects another.
// values: any[] — zero or more values to rotate through.
// returns: () => any
// A function. Each call returns the next value in order, wrapping
// from the last value back to the first. With NO values passed,
// the returned function returns undefined on every call.
function cycle(...values): () => any;
const next = cycle('a', 'b', 'c');
next(); // → 'a'
next(); // → 'b'
next(); // → 'c'
next(); // → 'a' (wrapped back to the start)
// Two independent cyclers — they do not share a position.
const a = cycle(1, 2);
const b = cycle(1, 2);
a(); // → 1
a(); // → 2
b(); // → 1 (b started fresh; a's calls didn't move it)
cycle(obj)() returns the same obj, not a copy.null, and undefined are all valid rotation members and must come back unchanged.cycle(...) call is independent. Two cyclers built from separate calls track separate positions; interleaving calls to them must not interfere.cycle() returns a function that returns undefined on every call and never throws.You'll return a function that remembers a position between calls, hands back the value at that position, then advances — wrapping around to the start once it passes the end.
Picture a playlist set to repeat. You press next: track 1. Next again: track 2. Next again: track 3. Press it once more and you're back at track 1 — it loops forever. cycle builds exactly that "next" button for whatever values you hand it. The values never change; what changes is where you are in the list. And because you might want several of these running at once (two playlists, two dealers handing out cards), each call to cycle(...) has to produce its own stepper with its own position — pressing next on one playlist must not skip a track on the other.
Hold two things in your head: the fixed list of values and a single index that says where you are. A call does three small things — read values[index], move the index forward, and wrap it if it ran off the end. The wrap is the whole trick, and it's one line: index = (index + 1) % values.length. The remainder operator % keeps the index in the range 0 … length - 1 — when index + 1 reaches length, the remainder is 0, so you land back on the first value. The index has to survive between calls, which is what a closure gives you: a function that remembers the variables from where it was defined.
The shape is right — keep an index, return a function that reads and advances it. But it's tempting to declare the index outside, as one variable the whole module shares:
let i = 0; // declared once, at module level
function cycle(...values) {
return function next() {
const value = values[i];
i = (i + 1) % values.length;
return value;
};
}
A single cycler looks fine. But every returned function reads and writes the same i. The moment you make two cyclers, they fight over one counter: const a = cycle('a', 'b'); const b = cycle('x', 'y'); a(); advances i to 1, so b() reads values[1] and returns 'y' instead of 'x'. Worse, the % values.length uses whichever cycler called last, so the wrap math is wrong too. The position must belong to each cycler, not to the module.
function cycle(...values) {
// `i` is declared INSIDE cycle, so every call to cycle(...) creates a fresh
// one. The returned function closes over THIS i — its own private position
// that no other cycler can see or move.
let i = 0;
return function next() {
// No values to rotate through: there's nothing to return, so hand back
// undefined every time instead of dividing by zero on the wrap below.
if (values.length === 0) return undefined;
const value = values[i];
// Advance, wrapping to 0 once we pass the last index. When i + 1 equals
// values.length, the remainder is 0 — back to the first value.
i = (i + 1) % values.length;
return value;
};
}
module.exports = { cycle };
The one change that matters is where i lives. Declared inside cycle, it's created anew on every call, and the returned next captures that specific i in its closure — so two cyclers get two independent positions. Reading values[i] before advancing means the first call returns the first value, not the second. And the empty-list guard is there because % 0 is NaN; without it the function would still not throw, but returning undefined up front states the intent and keeps the wrap math honest.
Trace const next = cycle('a', 'b', 'c') and four calls.
When cycle('a', 'b', 'c') runs, values is ['a', 'b', 'c'] and a fresh i = 0 is created. The returned next remembers both.
i = 0 next() → value = values[0] = 'a'
→ i = (0 + 1) % 3 = 1
→ returns 'a'
i = 1 next() → value = values[1] = 'b'
→ i = (1 + 1) % 3 = 2
→ returns 'b'
i = 2 next() → value = values[2] = 'c'
→ i = (2 + 1) % 3 = 0 ← wraps
→ returns 'c'
i = 0 next() → value = values[0] = 'a'
→ i = (0 + 1) % 3 = 1
→ returns 'a' ← back to the start
The third call is the interesting one: i is 2, we read 'c', then (2 + 1) % 3 is 0 — the index wraps. So the fourth call reads values[0] again and the loop restarts. If you built a second cycler with its own cycle(...) call, its i would start at 0 regardless of how many times you'd called next here.
cycle. A module-level let i = 0 is shared by every cycler, so two of them clobber each other's position. Fix: declare let i = 0 inside cycle so each call captures its own, then return the function that uses it.i = (i + 1) % n first and then return values[i], the very first call returns the second value and skips the first. Read values[i], save it, then advance.values.length is 0 and (i + 1) % 0 is NaN, so values[NaN] is undefined — it happens not to throw, but it's accidental. Guard if (values.length === 0) return undefined; so the empty case is deliberate.cycle(obj)() must return the same obj the caller passed, or object identity checks downstream break.undefined as "no more values". cycle(undefined, 'b') has undefined as a real member of the rotation. There's no done state here — undefined coming back means "the value at this position is undefined", not "the cycle ended".function* cycle(...values) { let i = 0; while (true) yield values[i++ % values.length]; } expresses the same idea with yield instead of a closure, and the caller drives it with gen.next().value. Worth comparing — generators are closures with built-in pause/resume.reset() handle. Return an object { next, reset } where reset sets i = 0, so a caller can jump back to the start without rebuilding the cycler. Same closed-over i, two functions sharing it.array.length fresh on each call, which is a subtly different (and trickier) contract.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
Implement cycle(...values) — given several values, return a function that hands back the next value every time you call it, looping forever once it runs off the end. Think of a playlist set to repeat: track 1, track 2, track 3, then back to track 1. The values you pass are fixed; the position advances on each call and wraps around. Each call to cycle(...) produces its own independent stepper — calling one never affects another.
// values: any[] — zero or more values to rotate through.
// returns: () => any
// A function. Each call returns the next value in order, wrapping
// from the last value back to the first. With NO values passed,
// the returned function returns undefined on every call.
function cycle(...values): () => any;
const next = cycle('a', 'b', 'c');
next(); // → 'a'
next(); // → 'b'
next(); // → 'c'
next(); // → 'a' (wrapped back to the start)
// Two independent cyclers — they do not share a position.
const a = cycle(1, 2);
const b = cycle(1, 2);
a(); // → 1
a(); // → 2
b(); // → 1 (b started fresh; a's calls didn't move it)
cycle(obj)() returns the same obj, not a copy.null, and undefined are all valid rotation members and must come back unchanged.cycle(...) call is independent. Two cyclers built from separate calls track separate positions; interleaving calls to them must not interfere.cycle() returns a function that returns undefined on every call and never throws.You'll return a function that remembers a position between calls, hands back the value at that position, then advances — wrapping around to the start once it passes the end.
Picture a playlist set to repeat. You press next: track 1. Next again: track 2. Next again: track 3. Press it once more and you're back at track 1 — it loops forever. cycle builds exactly that "next" button for whatever values you hand it. The values never change; what changes is where you are in the list. And because you might want several of these running at once (two playlists, two dealers handing out cards), each call to cycle(...) has to produce its own stepper with its own position — pressing next on one playlist must not skip a track on the other.
Hold two things in your head: the fixed list of values and a single index that says where you are. A call does three small things — read values[index], move the index forward, and wrap it if it ran off the end. The wrap is the whole trick, and it's one line: index = (index + 1) % values.length. The remainder operator % keeps the index in the range 0 … length - 1 — when index + 1 reaches length, the remainder is 0, so you land back on the first value. The index has to survive between calls, which is what a closure gives you: a function that remembers the variables from where it was defined.
The shape is right — keep an index, return a function that reads and advances it. But it's tempting to declare the index outside, as one variable the whole module shares:
let i = 0; // declared once, at module level
function cycle(...values) {
return function next() {
const value = values[i];
i = (i + 1) % values.length;
return value;
};
}
A single cycler looks fine. But every returned function reads and writes the same i. The moment you make two cyclers, they fight over one counter: const a = cycle('a', 'b'); const b = cycle('x', 'y'); a(); advances i to 1, so b() reads values[1] and returns 'y' instead of 'x'. Worse, the % values.length uses whichever cycler called last, so the wrap math is wrong too. The position must belong to each cycler, not to the module.
function cycle(...values) {
// `i` is declared INSIDE cycle, so every call to cycle(...) creates a fresh
// one. The returned function closes over THIS i — its own private position
// that no other cycler can see or move.
let i = 0;
return function next() {
// No values to rotate through: there's nothing to return, so hand back
// undefined every time instead of dividing by zero on the wrap below.
if (values.length === 0) return undefined;
const value = values[i];
// Advance, wrapping to 0 once we pass the last index. When i + 1 equals
// values.length, the remainder is 0 — back to the first value.
i = (i + 1) % values.length;
return value;
};
}
module.exports = { cycle };
The one change that matters is where i lives. Declared inside cycle, it's created anew on every call, and the returned next captures that specific i in its closure — so two cyclers get two independent positions. Reading values[i] before advancing means the first call returns the first value, not the second. And the empty-list guard is there because % 0 is NaN; without it the function would still not throw, but returning undefined up front states the intent and keeps the wrap math honest.
Trace const next = cycle('a', 'b', 'c') and four calls.
When cycle('a', 'b', 'c') runs, values is ['a', 'b', 'c'] and a fresh i = 0 is created. The returned next remembers both.
i = 0 next() → value = values[0] = 'a'
→ i = (0 + 1) % 3 = 1
→ returns 'a'
i = 1 next() → value = values[1] = 'b'
→ i = (1 + 1) % 3 = 2
→ returns 'b'
i = 2 next() → value = values[2] = 'c'
→ i = (2 + 1) % 3 = 0 ← wraps
→ returns 'c'
i = 0 next() → value = values[0] = 'a'
→ i = (0 + 1) % 3 = 1
→ returns 'a' ← back to the start
The third call is the interesting one: i is 2, we read 'c', then (2 + 1) % 3 is 0 — the index wraps. So the fourth call reads values[0] again and the loop restarts. If you built a second cycler with its own cycle(...) call, its i would start at 0 regardless of how many times you'd called next here.
cycle. A module-level let i = 0 is shared by every cycler, so two of them clobber each other's position. Fix: declare let i = 0 inside cycle so each call captures its own, then return the function that uses it.i = (i + 1) % n first and then return values[i], the very first call returns the second value and skips the first. Read values[i], save it, then advance.values.length is 0 and (i + 1) % 0 is NaN, so values[NaN] is undefined — it happens not to throw, but it's accidental. Guard if (values.length === 0) return undefined; so the empty case is deliberate.cycle(obj)() must return the same obj the caller passed, or object identity checks downstream break.undefined as "no more values". cycle(undefined, 'b') has undefined as a real member of the rotation. There's no done state here — undefined coming back means "the value at this position is undefined", not "the cycle ended".function* cycle(...values) { let i = 0; while (true) yield values[i++ % values.length]; } expresses the same idea with yield instead of a closure, and the caller drives it with gen.next().value. Worth comparing — generators are closures with built-in pause/resume.reset() handle. Return an object { next, reset } where reset sets i = 0, so a caller can jump back to the start without rebuilding the cycler. Same closed-over i, two functions sharing it.array.length fresh on each call, which is a subtly different (and trickier) contract.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.