The TypeScript compiler is failing because it encountered an object that it expected to be iterable but wasn’t.

This usually happens when you’re trying to use a collection type in a context that expects iteration (like a for...of loop or the spread operator ...) and that collection type doesn’t implement the Symbol.iterator method. TypeScript’s type system is catching this at compile time, preventing a runtime TypeError.

Here are the most common reasons and how to fix them:

1. Using a Plain Object in a for...of Loop

You’ve tried to iterate directly over a plain JavaScript object. Plain objects are not inherently iterable in JavaScript.

Diagnosis: Look for for (const item of myObject) { ... } where myObject is defined as { key: 'value' } or { a: 1, b: 2 }.

Fix: If you intend to iterate over the values of the object, use Object.values(). If you intend to iterate over the keys, use Object.keys(). If you intend to iterate over key-value pairs, use Object.entries().

const myObject = { a: 1, b: 2, c: 3 };

// To iterate over values:
for (const value of Object.values(myObject)) {
  console.log(value); // 1, 2, 3
}

// To iterate over keys:
for (const key of Object.keys(myObject)) {
  console.log(key); // 'a', 'b', 'c'
}

// To iterate over entries:
for (const [key, value] of Object.entries(myObject)) {
  console.log(`${key}: ${value}`); // 'a: 1', 'b: 2', 'c: 3'
}

Why it works: Object.values(), Object.keys(), and Object.entries() all return arrays. Arrays are iterable and have a Symbol.iterator method.

2. Missing Symbol.iterator on a Custom Class

You’ve created a custom class that you expect to be iterable (e.g., to use in for...of loops or with the spread operator), but you haven’t defined the [Symbol.iterator]() method.

Diagnosis: Check your class definition for a method named [Symbol.iterator].

Fix: Implement the [Symbol.iterator]() method in your class. This method must return an iterator object, which has a next() method. The next() method should return an object with value and done properties.

class MyCollection<T> {
  private items: T[] = [];

  addItem(item: T) {
    this.items.push(item);
  }

  // Implement the iterator protocol
  [Symbol.iterator](): Iterator<T> {
    let index = 0;
    return {
      next: (): IteratorResult<T> => {
        if (index < this.items.length) {
          return { value: this.items[index++], done: false };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
}

const collection = new MyCollection<number>();
collection.addItem(10);
collection.addItem(20);

for (const item of collection) {
  console.log(item); // 10, 20
}

Why it works: By providing the [Symbol.iterator] method, your class now conforms to the iterable protocol, allowing JavaScript and TypeScript to iterate over its instances.

3. Incorrectly Typed Array or Iterable

You might be passing an array or another iterable type to a function that expects a specific iterable, but the type definition is slightly off, or you’re accidentally passing a non-iterable value that looks like an array (e.g., an Arguments object in older JavaScript or a specific DOM collection that isn’t directly iterable).

Diagnosis: Examine the type signature of the function or method you’re calling. Check if the parameter is typed as Iterable<T>, T[], or a specific collection type that is known to be iterable. Ensure the actual argument you’re passing matches this type.

Fix: If you’re passing a non-iterable that you want to be treated as iterable, convert it to an array first. For example, if you have an HTMLCollection (which is array-like but not directly iterable with for...of), you can convert it:

const elements: HTMLCollection = document.getElementsByTagName('div');

// Incorrect (will cause TS2488 if used with for...of directly)
// for (const element of elements) { ... }

// Correct: Convert to an array
const elementArray = Array.from(elements);
for (const element of elementArray) {
  console.log(element);
}

// Or using spread syntax (if the collection is compatible)
// const elementArray = [...elements]; // This might not work for all array-like objects

Why it works: Array.from() creates a new Array instance from an array-like or iterable object. Arrays are always iterable.

4. Using Array.prototype.map or filter on Non-Arrays

You’ve tried to use array methods like map, filter, or reduce on something that isn’t an array, but it might be iterable. However, these methods are specifically on the Array.prototype, not on the general iterable protocol.

Diagnosis: You’ll see calls like myNonArray.map(item => item * 2).

Fix: Ensure the object you’re calling .map() on is actually an array or convert it to one.

// Example: A Set is iterable, but doesn't have .map() directly
const mySet = new Set([1, 2, 3]);

// Incorrect
// const doubled = mySet.map(x => x * 2); // TS2339: Property 'map' does not exist on type 'Set<number>'

// Correct: Convert to array first
const doubled = Array.from(mySet).map(x => x * 2);
console.log(doubled); // [2, 4, 6]

// Or using spread syntax
const doubledSpread = [...mySet].map(x => x * 2);
console.log(doubledSpread); // [2, 4, 6]

Why it works: Array.from() or the spread syntax [...] creates a new array, which then has access to all the standard array methods like map.

5. Incorrectly Typed IterableIterator

Sometimes, you might be dealing with a function that returns an IterableIterator, and you might be mistyping the return value or how you’re consuming it.

Diagnosis: If you have a function that returns IterableIterator<T> and you try to use it in a way that implies it’s a simple array or a plain object, you might hit this.

Fix: Understand that IterableIterator<T> is both an Iterable<T> (meaning it can be used in for...of loops and spread) and an Iterator<T> (meaning it has a .next() method). Usually, you’d consume it as an iterable. If you need to collect all its values into an array, use Array.from() or the spread operator.

function* myGenerator(): IterableIterator<string> {
  yield "hello";
  yield "world";
}

const iterator = myGenerator();

// Correct usage as an iterable:
for (const word of iterator) {
  console.log(word); // "hello", "world"
}

// Or collect into an array:
const wordsArray = Array.from(myGenerator()); // Create a new generator instance to iterate again
console.log(wordsArray); // ["hello", "world"]

const wordsSpread = [...myGenerator()]; // Create a new generator instance to iterate again
console.log(wordsSpread); // ["hello", "world"]

Why it works: Array.from() and the spread syntax are designed to consume any iterable, including IterableIterators, and produce a standard array.

The next error you’ll likely encounter after fixing this is related to a missing Symbol.toStringTag if you try to use Object.prototype.toString.call() on a custom iterable object and want a specific tag.

Want structured learning?

Take the full Typescript course →