---
title: Iterators
source: 'https://academia.sh/en/courses/javascript-object-model/iterators'
course: 'Objects and Functions in JavaScript'
language: en
updated: '2026-08-23T07:00:59+00:00'
license: 'CC BY-SA 4.0'
---

# Iterators

The iteration protocol's two contracts, making a type traversable through its prototype, the distinction between iterator and iterable, cleanup on early exit, and lazy production.

In the previous lesson, a `for...of` attempt on `WeakMap` threw an error, while the same
loop works on `Map`, `Set`, an array, and a string. The difference is not a list of
types: the `for...of` loop, the spread operator, destructuring, and `Array.from` all look
at a single contract.

The **iteration protocol** consists of two contracts. If an object offers a method named
`[Symbol.iterator]`, it is **iterable**; that method has to return an **iterator**
offering `next`. The lesson first sets up the contract by hand, then binds the
measurement series to this contract.

## The next Contract

An iterator is an ordinary object with a single method. A `next` call returns a two-field
object: the next value, and whether the sequence has ended.

```js
function measurementIterator(values) {
  let index = 0;
  return {
    next() {
      if (index >= values.length) return { value: undefined, done: true };
      const value = values[index];
      index += 1;
      return { value, done: false };
    },
  };
}

const iterator = measurementIterator([21.4, 19.8, 25.1]);

console.log(JSON.stringify(iterator.next()));
console.log(JSON.stringify(iterator.next()));
console.log(JSON.stringify(iterator.next()));
console.log(JSON.stringify(iterator.next()));

const second = measurementIterator([21.4, 19.8]);
let step = second.next();
const collected = [];
while (!step.done) {
  collected.push(step.value);
  step = second.next();
}
console.log(collected.join(","));
console.log(typeof iterator[Symbol.iterator]);
```

```
{"value":21.4,"done":false}
{"value":19.8,"done":false}
{"value":25.1,"done":false}
{"done":true}
21.4,19.8
undefined
```

Position information is held in the `index` variable and is invisible from outside — the
pattern from the Closures lesson works here too. The `value` field not appearing in the
fourth output is a result of serialization: fields with an `undefined` value do not enter
`JSON.stringify` output.

The `while` loop is a `for...of` loop written by hand. The last line tells you why this
object cannot be used with `for...of`: it offers `next` but not `[Symbol.iterator]`, so
it is an iterator but not iterable.

## Making a Type Iterable

The second contract is an object offering a `[Symbol.iterator]` method. When the method
is written in a class body, it stands on the prototype and all instances become
iterable.

```js
class MeasurementSeries {
  constructor(sensor, values) {
    this.sensor = sensor;
    this.values = values;
  }

  [Symbol.iterator]() {
    let index = 0;
    const values = this.values;
    return {
      next() {
        if (index >= values.length) return { value: undefined, done: true };
        const value = values[index];
        index += 1;
        return { value, done: false };
      },
    };
  }
}

const series = new MeasurementSeries("S-01", [21.4, 19.8, 25.1]);

const collected = [];
for (const value of series) collected.push(value);
console.log(collected.join(","));

console.log([...series].join(","));
console.log(Array.from(series).length);

const [first, second] = series;
console.log(`${first} ${second}`);

console.log(Math.max(...series));
console.log(Object.hasOwn(series, Symbol.iterator));
console.log(Object.hasOwn(MeasurementSeries.prototype, Symbol.iterator));
console.log(Object.keys(series).join(","));
console.log(JSON.stringify(series));
```

```
21.4,19.8,25.1
21.4,19.8,25.1
3
21.4 19.8
25.1
false
true
sensor,values
{"sensor":"S-01","values":[21.4,19.8,25.1]}
```

One method was added; five separate language features started working. `for...of`,
spread, `Array.from`, array destructuring, and argument spreading all use the same
contract.

The sixth and seventh lines tie back to this lesson's axis: the method stands not on the
instance, but on `MeasurementSeries.prototype`. Iterability is not a flag belonging to
the object, it is a method found on the chain. This is why making a type iterable
afterward is possible by adding this method to its prototype.

Because it is symbol-keyed, it does not appear in `Object.keys` output and does not enter
serialization; this property of symbol keys, covered in the Property Descriptors lesson,
keeps protocol methods separate from data fields.

## The Iterator–Iterable Distinction

The two contracts most often merge in the same object. Built-in iterators offer a
`[Symbol.iterator]` method that returns themselves, which is what makes them both
iterator and iterable.

```js
const values = [21.4, 19.8, 25.1];

const iterator = values[Symbol.iterator]();
console.log(typeof iterator.next);
console.log(typeof iterator[Symbol.iterator]);
console.log(iterator[Symbol.iterator]() === iterator);

console.log(JSON.stringify(iterator.next()));

const remaining = [];
for (const value of iterator) remaining.push(value);
console.log(remaining.join(","));

const emptyReturn = [];
for (const value of iterator) emptyReturn.push(value);
console.log(emptyReturn.length);

const firstPass = [];
for (const value of values) firstPass.push(value);
const secondPass = [];
for (const value of values) secondPass.push(value);
console.log(firstPass.length === secondPass.length);

const text = "measurement";
console.log([...text].join("-"));
const map = new Map([["S-01", 21.4]]);
console.log([...map][0].join("="));
```

```
function
function
true
{"value":21.4,"done":false}
19.8,25.1
0
true
m-e-a-s-u-r-e-m-e-n-t
S-01=21.4
```

The practical consequence of the distinction is exhaustion. Traversing an iterator
**consumes** it: the first `for...of` reads the remaining two values, the second finds
nothing. An iterable object, on the other hand, produces a new iterator on every
traversal, which is why it can be traversed over and over.

This explains why it matters whether an iterable object or an iterator is given to a
function. If an iterator is given to a function that needs to traverse it twice, the
second pass returns empty; no error message is produced either.

The last two lines show how widespread the protocol is. Traversing a string gives code
points, not code units — the distinction introduced in the Character Encodings lesson of
the How Computers Work course is observable here. Traversing a `Map` gives key–value
pairs.

## Early Exit and Cleanup

When a `for...of` loop is ended with `break`, or an error is thrown, the protocol calls
the iterator's `return` method. This is the reserved point for closing open resources.

```js
const log = [];

function resourceIterator(values) {
  let index = 0;
  let closed = false;
  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      if (closed || index >= values.length) return { value: undefined, done: true };
      const value = values[index];
      index += 1;
      return { value, done: false };
    },
    return(value) {
      closed = true;
      log.push(`resource closed, read=${index}`);
      return { value, done: true };
    },
  };
}

const resource = resourceIterator([21.4, 19.8, 25.1, 18.2]);

for (const value of resource) {
  if (value > 20 && value < 22) {
    console.log(`found: ${value}`);
    break;
  }
}

console.log(log.join(" | "));
console.log(JSON.stringify(resource.next()));

const secondResource = resourceIterator([21.4, 19.8]);
const [singleValue] = secondResource;
console.log(singleValue);
console.log(log.length);
```

```
found: 21.4
resource closed, read=1
{"done":true}
21.4
2
```

A `break` statement does not just break the loop, it also notifies the iterator. A closed
iterator answers as finished on subsequent `next` calls. The last section shows the same
call is made during array destructuring too: once the requested number of values is
taken, the resource is closed.

This behavior matters for iterators bound to resources like file and network streams;
early exit does not leave the resource open.

## Lazy Production

The protocol's last property is that values are produced **as requested**. Even if the
source is an array, the computation done on each value only runs when that value is
read.

```js
let computeCount = 0;

function calibratedSeries(rawValues, offset) {
  return {
    [Symbol.iterator]() {
      let index = 0;
      return {
        [Symbol.iterator]() {
          return this;
        },
        next() {
          if (index >= rawValues.length) return { value: undefined, done: true };
          computeCount += 1;
          const value = rawValues[index] + offset;
          index += 1;
          return { value: Number(value.toFixed(2)), done: false };
        },
      };
    },
  };
}

const series = calibratedSeries([21.4, 19.8, 25.1, 18.2, 30.0], -0.4);

console.log(computeCount);

let firstAboveThreshold;
for (const value of series) {
  if (value > 24) {
    firstAboveThreshold = value;
    break;
  }
}

console.log(firstAboveThreshold);
console.log(computeCount);

const all = [...series];
console.log(all.join(","));
console.log(computeCount);
```

```
0
24.7
3
21,19.4,24.7,17.8,29.6
8
```

No computation has been done when the series is set up. Three computations run until the
first value above the threshold is found; the remaining two values are never computed. When
the same series is traversed from the start again, five more computations run and the
total climbs to eight.

If the same job were written with `map`, all five computations would run up front. This
is the **lazy evaluation** introduced in the Introduction to Functional Programming
lesson of the Programming Fundamentals course, and it is the only workable approach for
large or endless sequences.

The cost of writing the protocol by hand is also visible in this example: position, the
end condition, and the self-returning `[Symbol.iterator]` method are repeated every
time. The next lesson introduces a syntax that eliminates this repetition.

## Summary

- The iteration protocol consists of two contracts: an iterable object offers
  `[Symbol.iterator]`, an iterator offers `next`.
- `for...of`, spread, destructuring, `Array.from`, and argument spreading all use the
  same contract; adding a single method makes all of them work.
- `[Symbol.iterator]` written in a class body stands on the prototype; iterability is a
  method found on the chain.
- An iterator gets exhausted, an iterable object produces a new iterator on every
  traversal; this distinction is decisive wherever something needs to be traversed twice.
- `break` and early destructuring call the iterator's `return` method, allowing for
  resource cleanup.
- Values are produced as requested; an unread value is never computed at all.

## Next Step

The same three pieces kept repeating in hand-written iterators: the variable holding
position, the end condition, and the self-returning `[Symbol.iterator]` method. The
language offers a function type that produces all three itself — functions that can pause
their execution and resume where they left off. The next lesson takes up generators and
reduces this lesson's lazy series to a few lines.
