+TypeScript example
Playlist owns the items; each iterator owns its current position. Keeping position out of the collection lets two traversals progress independently.
// Hide the internal array and expose only the traversal protocol.
class Playlist implements Iterable<string> {
private readonly tracks: readonly string[];
constructor(tracks: readonly string[]) { this.tracks = [...tracks]; }
// Each call returns a generator with its own traversal position.
*[Symbol.iterator](): Generator<string, void, unknown> {
for (const track of this.tracks) yield track;
}
}
function step(iterator: Iterator<string>): string {
const result = iterator.next();
return result.done ? "(end)" : result.value;
}
const playlist = new Playlist(["intro", "outro"]);
const first = playlist[Symbol.iterator]();
const second = playlist[Symbol.iterator]();
console.log("first: " + step(first));
console.log("first: " + step(first));
console.log("second: " + step(second));
console.log("second: " + step(second));
first: intro
first: outro
second: intro
second: outro
Symbol.iterator provides the entry point corresponding to C# GetEnumerator(). Each generator call creates a separate iterator; after traversal, check the done flag returned by next().
Use LINQ for filtering and transformation
An existing iterable needs no new iterator class just to filter and transform its items. C# LINQ composes traversal by receiving a predicate in Where and a transformation function in Select. This example uppercases only track names beginning with in and leaves the input array unchanged.
C#
using System;
using System.Linq;
var tracks = new[] { "intro", "outro", "interlude" };
// Where filters, Select transforms, and ToArray enumerates and stores the results now.
string[] labels = tracks
.Where(track => track.StartsWith("in", StringComparison.Ordinal))
.Select(track => track.ToUpperInvariant())
.ToArray();
Console.WriteLine(string.Join(",", labels));
Console.WriteLine(string.Join(",", tracks));
TypeScript
const tracks = ["intro", "outro", "interlude"];
// Array filter and map evaluate immediately and return new arrays.
const labels = tracks
.filter(track => track.startsWith("in"))
.map(track => track.toUpperCase());
console.log(labels.join(","));
console.log(tracks.join(","));
INTRO,INTERLUDE
intro,outro,interlude
These examples use strings in memory. LINQ Where and Select run during enumeration, with ToArray choosing when to enumerate; TypeScript array filter and map evaluate immediately. Both select and uppercase the same ASCII track names here without modifying the source. Functional syntax alone does not prevent external calls or shared-state mutation.
Microsoft: Introduction to LINQ queries