An iterator is an object that defines a sequence and potentially a return value upon its termination. It provides a standard way to access elements from a collection one at a time, without exposing the underlying representation. Iterators are the backbone of JavaScript's for...of loops, spread syntax, and destructuring.
JavaScript iterators follow a simple protocol. An object is an iterator when it implements a next() method with the following signature:
iterator.next() // Returns: { value: any, done: boolean }| Property | Description |
|---|---|
value |
The current value in the sequence |
done |
true if the iterator has reached the end, false otherwise |
function createCounterIterator(start, end) {
let current = start;
return {
next: function() {
if (current <= end) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
const counter = createCounterIterator(1, 3);
console.log(counter.next()); // { value: 1, done: false }
console.log(counter.next()); // { value: 2, done: false }
console.log(counter.next()); // { value: 3, done: false }
console.log(counter.next()); // { value: undefined, done: true }An iterable is an object that defines its own iteration behavior. It must implement a [Symbol.iterator] method that returns an iterator.
const iterable = {
[Symbol.iterator]: function() {
let step = 0;
return {
next: function() {
step++;
if (step === 1) return { value: "Hello", done: false };
if (step === 2) return { value: "World", done: false };
return { value: undefined, done: true };
}
};
}
};
// Now we can use for...of!
for (const item of iterable) {
console.log(item);
}
// "Hello"
// "World"JavaScript has several built-in iterables:
| Object | Iterates Over |
|---|---|
String |
Characters |
Array |
Elements |
Set |
Unique values |
Map |
[key, value] pairs |
TypedArray |
Elements |
arguments |
Arguments passed to a function |
NodeList |
DOM nodes |
// String
for (const char of "Hello") {
console.log(char); // H, e, l, l, o
}
// Array
for (const item of [10, 20, 30]) {
console.log(item); // 10, 20, 30
}
// Set
for (const item of new Set([1, 2, 2, 3])) {
console.log(item); // 1, 2, 3
}
// Map
for (const [key, value] of new Map([["a", 1], ["b", 2]])) {
console.log(`${key}: ${value}`); // a: 1, b: 2
}class Range {
constructor(start, end, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
const step = this.step;
return {
next() {
if (current <= end) {
const value = current;
current += step;
return { value, done: false };
}
return { value: undefined, done: true };
}
};
}
}
const range = new Range(1, 5);
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
// With spread
console.log([...new Range(10, 20, 5)]); // [10, 15, 20]function createFibonacciIterator() {
let a = 0, b = 1;
return {
next: function() {
const current = a;
a = b;
b = current + b;
return { value: current, done: false };
}
};
}
const fib = createFibonacciIterator();
for (let i = 0; i < 10; i++) {
console.log(fib.next().value);
}
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34const arr = ["a", "b", "c"];
// keys() — returns iterator for indices
for (const key of arr.keys()) {
console.log(key); // 0, 1, 2
}
// values() — returns iterator for values
for (const value of arr.values()) {
console.log(value); // a, b, c
}
// entries() — returns iterator for [index, value] pairs
for (const [index, value] of arr.entries()) {
console.log(`${index}: ${value}`);
// 0: a, 1: b, 2: c
}const map = new Map([["a", 1], ["b", 2], ["c", 3]]);
// keys()
for (const key of map.keys()) {
console.log(key); // a, b, c
}
// values()
for (const value of map.values()) {
console.log(value); // 1, 2, 3
}
// entries()
for (const entry of map.entries()) {
console.log(entry); // ["a", 1], ["b", 2], ["c", 3]
}const set = new Set([1, 2, 3]);
for (const value of set.values()) {
console.log(value); // 1, 2, 3
}
// Sets don't have keys — keys() returns the same as values()
for (const key of set.keys()) {
console.log(key); // 1, 2, 3
}You don't have to use for...of. You can manually call next():
const arr = [10, 20, 30];
const iterator = arr[Symbol.iterator]();
let result = iterator.next();
while (!result.done) {
console.log(result.value);
result = iterator.next();
}
// 10, 20, 30This is useful when you need more control over the iteration process.
Destructuring uses the iterator protocol under the hood:
const arr = [1, 2, 3, 4, 5];
// Array destructuring
const [first, second, ...rest] = arr;
console.log(first, second, rest); // 1, 2, [3, 4, 5]
// String destructuring
const [a, b, c] = "Hello";
console.log(a, b, c); // H, e, l
// With custom iterables
const range = new Range(100, 500, 100);
const [hundreds, twoHundreds, threeHundreds] = range;
console.log(hundreds, twoHundreds, threeHundreds); // 100, 200, 300const range = new Range(1, 5);
// Spread into array
const arr = [...range];
console.log(arr); // [1, 2, 3, 4, 5]
// Spread into function arguments
console.log(Math.max(...new Range(1, 10))); // 10
// Combine iterables
const combined = [..."ab", ...new Range(1, 3)];
console.log(combined); // ["a", "b", 1, 2, 3]const userMap = new Map([
["id", 1],
["name", "Alice"],
["role", "admin"]
]);
// Convert map to object
const user = Object.fromEntries(userMap);
console.log(user); // { id: 1, name: "Alice", role: "admin" }
// Iterate with destructuring
for (const [key, value] of userMap) {
console.log(`${key}: ${value}`);
}Objects are not iterable by default. You can make them iterable by adding [Symbol.iterator]:
const team = {
lead: "Alice",
developer: "Bob",
designer: "Charlie",
[Symbol.iterator]() {
const roles = Object.keys(this);
const values = Object.values(this);
let index = 0;
return {
next: () => {
if (index < values.length) {
return {
value: { role: roles[index], name: values[index++] },
done: false
};
}
return { done: true };
}
};
}
};
for (const member of team) {
console.log(`${member.role}: ${member.name}`);
}
// lead: Alice
// developer: Bob
// designer: CharlieAdvanced iterators can implement return() and throw() methods:
function createResourceIterator() {
let resource = { open: true, data: [1, 2, 3] };
let index = 0;
return {
next() {
if (index < resource.data.length) {
return { value: resource.data[index++], done: false };
}
return { done: true };
},
return() {
resource.open = false;
console.log("Resource cleaned up!");
return { done: true };
}
};
}
const iterable = {
[Symbol.iterator]: createResourceIterator
};
for (const value of iterable) {
console.log(value);
if (value === 2) break; // Triggers return()!
}
// 1
// 2
// Resource cleaned up!const arr = [1, 2, 3, 4, 5];
for (const num of arr) {
if (num === 3) {
arr.push(6); // ❌ Don't modify during iteration!
}
console.log(num);
}const obj = { a: 1, b: 2 };
// ❌ Error! Objects are not iterable
// for (const item of obj) { ... }
// ✅ Use Object.keys(), Object.values(), or Object.entries()
for (const [key, value] of Object.entries(obj)) {
console.log(`${key}: ${value}`);
}const range = new Range(1, 3);
// ✅ Each for...of creates a fresh iterator
for (const num of range) console.log(num); // 1, 2, 3
for (const num of range) console.log(num); // 1, 2, 3
// ❌ Manual iterator is consumed
const iterator = range[Symbol.iterator]();
iterator.next(); // { value: 1, done: false }
iterator.next(); // { value: 2, done: false }
// iterator is now at position 3Create an iterable that iterates an array in reverse.
const arr = [1, 2, 3, 4, 5];
for (const item of reverse(arr)) {
console.log(item); // 5, 4, 3, 2, 1
}Create a function that takes only the first N values from an iterator.
const fib = createFibonacciIterator();
for (const num of take(fib, 5)) {
console.log(num); // 0, 1, 1, 2, 3
}Combine two iterables element by element.
const names = ["Alice", "Bob", "Charlie"];
const ages = [25, 30, 35];
for (const [name, age] of zip(names, ages)) {
console.log(`${name} is ${age}`);
}Create a filtered iterator that only yields matching values.
const numbers = [1, 2, 3, 4, 5, 6];
for (const num of filter(numbers, n => n % 2 === 0)) {
console.log(num); // 2, 4, 6
}Create an iterable that simulates paginated API results.
const pages = createPaginatedIterator("/api/users", 10);
for (const page of pages) {
console.log(`Loaded ${page.length} users`);
}- An iterator is an object with a
next()method returning{ value, done } - An iterable is an object with a
[Symbol.iterator]method returning an iterator - Built-in iterables:
String,Array,Set,Map,TypedArray for...of, spread syntax, and destructuring all use the iterator protocol- You can create custom iterables by implementing
[Symbol.iterator] - Iterators can implement
return()for cleanup when iteration ends early - Objects are not iterable by default — use
Object.entries()or add[Symbol.iterator]
Iterators lead naturally into:
- Generators — cleaner syntax for creating iterators
- Async Iterators — iterating over async data sources
- Functional Programming — map, filter, reduce with iterables
Happy coding! 🚀