Back to Blog
JavaScriptIteratorsProtocolsES6

JavaScript Iterator Protocol Guide

Master JavaScript iterators and the iteration protocol for custom iterable objects.

B
Bootspring Team
Engineering
February 23, 2020
6 min read

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// - NodeLists

Creating 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.

Share this article

Help spread the word about Bootspring