The iterator protocol defines how to produce a sequence of values. Here's how to implement and use it.
Iterator Basics#
1// Built-in iterables
2const array = [1, 2, 3];
3const iterator = array[Symbol.iterator]();
4
5console.log(iterator.next()); // { value: 1, done: false }
6console.log(iterator.next()); // { value: 2, done: false }
7console.log(iterator.next()); // { value: 3, done: false }
8console.log(iterator.next()); // { value: undefined, done: true }
9
10// Using for...of (calls iterator internally)
11for (const value of array) {
12 console.log(value);
13}
14
15// Spread operator uses iterator
16const copy = [...array];
17
18// Destructuring uses iterator
19const [first, second] = array;
20
21// Built-in iterables:
22// - Arrays
23// - Strings
24// - Maps
25// - Sets
26// - TypedArrays
27// - arguments object
28// - NodeListsCreating Custom Iterators#
1// Manual iterator
2const range = {
3 from: 1,
4 to: 5,
5
6 [Symbol.iterator]() {
7 let current = this.from;
8 const last = this.to;
9
10 return {
11 next() {
12 if (current <= last) {
13 return { value: current++, done: false };
14 }
15 return { done: true };
16 }
17 };
18 }
19};
20
21for (const num of range) {
22 console.log(num); // 1, 2, 3, 4, 5
23}
24
25// Using generator (simpler)
26const rangeGenerator = {
27 from: 1,
28 to: 5,
29
30 *[Symbol.iterator]() {
31 for (let i = this.from; i <= this.to; i++) {
32 yield i;
33 }
34 }
35};
36
37// Iterator class
38class Counter {
39 constructor(start, end) {
40 this.start = start;
41 this.end = end;
42 }
43
44 [Symbol.iterator]() {
45 let count = this.start;
46 const end = this.end;
47
48 return {
49 next() {
50 if (count <= end) {
51 return { value: count++, done: false };
52 }
53 return { done: true };
54 },
55
56 // Optional: return method for cleanup
57 return() {
58 console.log('Iterator closed early');
59 return { done: true };
60 }
61 };
62 }
63}
64
65const counter = new Counter(1, 3);
66for (const n of counter) {
67 console.log(n); // 1, 2, 3
68}Iterator Methods#
1// Iterator with return and throw
2function createIterator(items) {
3 let index = 0;
4
5 return {
6 next() {
7 if (index < items.length) {
8 return { value: items[index++], done: false };
9 }
10 return { done: true };
11 },
12
13 // Called when iteration stops early (break, return, throw)
14 return(value) {
15 console.log('Cleanup performed');
16 return { value, done: true };
17 },
18
19 // Called when error is thrown into iterator
20 throw(error) {
21 console.log('Error handled:', error);
22 return { done: true };
23 },
24
25 // Make iterator itself iterable
26 [Symbol.iterator]() {
27 return this;
28 }
29 };
30}
31
32// Early termination
33const iter = createIterator([1, 2, 3, 4, 5]);
34for (const value of iter) {
35 console.log(value);
36 if (value === 3) break; // Triggers return()
37}Practical Examples#
1// Linked list iterator
2class LinkedList {
3 constructor() {
4 this.head = null;
5 }
6
7 add(value) {
8 const node = { value, next: null };
9 if (!this.head) {
10 this.head = node;
11 } else {
12 let current = this.head;
13 while (current.next) {
14 current = current.next;
15 }
16 current.next = node;
17 }
18 return this;
19 }
20
21 *[Symbol.iterator]() {
22 let current = this.head;
23 while (current) {
24 yield current.value;
25 current = current.next;
26 }
27 }
28}
29
30const list = new LinkedList();
31list.add(1).add(2).add(3);
32
33for (const value of list) {
34 console.log(value); // 1, 2, 3
35}
36
37// Tree iterator (depth-first)
38class TreeNode {
39 constructor(value, children = []) {
40 this.value = value;
41 this.children = children;
42 }
43
44 *[Symbol.iterator]() {
45 yield this.value;
46 for (const child of this.children) {
47 yield* child; // Delegate to child iterator
48 }
49 }
50}
51
52const tree = new TreeNode(1, [
53 new TreeNode(2, [
54 new TreeNode(4),
55 new TreeNode(5)
56 ]),
57 new TreeNode(3)
58]);
59
60console.log([...tree]); // [1, 2, 4, 5, 3]
61
62// Paginated data iterator
63async function* fetchPages(url) {
64 let page = 1;
65 let hasMore = true;
66
67 while (hasMore) {
68 const response = await fetch(`${url}?page=${page}`);
69 const data = await response.json();
70
71 yield* data.items;
72
73 hasMore = data.hasNextPage;
74 page++;
75 }
76}
77
78// Usage
79for await (const item of fetchPages('/api/items')) {
80 console.log(item);
81}Infinite Iterators#
1// Infinite sequence
2function* naturals() {
3 let n = 1;
4 while (true) {
5 yield n++;
6 }
7}
8
9// Take first N values
10function take(iterable, n) {
11 const result = [];
12 let count = 0;
13
14 for (const value of iterable) {
15 if (count >= n) break;
16 result.push(value);
17 count++;
18 }
19
20 return result;
21}
22
23console.log(take(naturals(), 5)); // [1, 2, 3, 4, 5]
24
25// Fibonacci sequence
26function* fibonacci() {
27 let [a, b] = [0, 1];
28 while (true) {
29 yield a;
30 [a, b] = [b, a + b];
31 }
32}
33
34console.log(take(fibonacci(), 10));
35// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
36
37// Random values
38function* randomValues(min, max) {
39 while (true) {
40 yield Math.floor(Math.random() * (max - min + 1)) + min;
41 }
42}Iterator Utilities#
1// Map over iterator
2function* map(iterable, fn) {
3 for (const value of iterable) {
4 yield fn(value);
5 }
6}
7
8// Filter iterator
9function* filter(iterable, predicate) {
10 for (const value of iterable) {
11 if (predicate(value)) {
12 yield value;
13 }
14 }
15}
16
17// Take while condition is true
18function* takeWhile(iterable, predicate) {
19 for (const value of iterable) {
20 if (!predicate(value)) break;
21 yield value;
22 }
23}
24
25// Skip first N
26function* skip(iterable, n) {
27 let count = 0;
28 for (const value of iterable) {
29 if (count >= n) {
30 yield value;
31 }
32 count++;
33 }
34}
35
36// Chain iterables
37function* chain(...iterables) {
38 for (const iterable of iterables) {
39 yield* iterable;
40 }
41}
42
43// Zip iterables
44function* zip(...iterables) {
45 const iterators = iterables.map(i => i[Symbol.iterator]());
46
47 while (true) {
48 const results = iterators.map(i => i.next());
49
50 if (results.some(r => r.done)) {
51 return;
52 }
53
54 yield results.map(r => r.value);
55 }
56}
57
58// Usage
59const numbers = [1, 2, 3, 4, 5];
60const doubled = map(numbers, n => n * 2);
61const evens = filter(doubled, n => n % 4 === 0);
62
63console.log([...evens]); // [4, 8]
64
65console.log([...zip([1, 2, 3], ['a', 'b', 'c'])]);
66// [[1, 'a'], [2, 'b'], [3, 'c']]String Iterators#
1// Strings are iterable
2const str = 'Hello';
3
4for (const char of str) {
5 console.log(char); // H, e, l, l, o
6}
7
8// Handles Unicode correctly
9const emoji = 'šš';
10console.log([...emoji]); // ['š', 'š']
11console.log(emoji.length); // 4 (code units)
12console.log([...emoji].length); // 2 (graphemes)
13
14// Custom string iterator
15class ReversibleString {
16 constructor(str) {
17 this.str = str;
18 }
19
20 *[Symbol.iterator]() {
21 yield* this.str;
22 }
23
24 *reverse() {
25 for (let i = this.str.length - 1; i >= 0; i--) {
26 yield this.str[i];
27 }
28 }
29}
30
31const rs = new ReversibleString('Hello');
32console.log([...rs]); // ['H', 'e', 'l', 'l', 'o']
33console.log([...rs.reverse()]); // ['o', 'l', 'l', 'e', 'H']Best Practices#
Implementation:
ā Use generators when possible
ā Implement return() for cleanup
ā Make iterators themselves iterable
ā Handle edge cases gracefully
Usage:
ā Use for...of for iteration
ā Use spread for collecting values
ā Compose with utility functions
ā Lazy evaluation for large data
Performance:
ā Prefer lazy iteration
ā Avoid collecting infinite iterators
ā Use take() to limit iteration
ā Clean up resources properly
Avoid:
ā Forgetting Symbol.iterator
ā Mutating while iterating
ā Infinite loops without limits
ā Complex state in iterators
Conclusion#
The iterator protocol enables custom iteration behavior for any object. Use Symbol.iterator to make objects iterable, generators for simple implementation, and utility functions for composition. Iterators provide lazy evaluation and clean integration with for...of, spread, and destructuring.