error TS2488: Type '{ theme: string; language: string; }' must have a '[Symbol.iterator]()' method that returns an iterator. is reported when for...of, array destructuring (const [a] = value) or array spread ([...value]) is used on a value that cannot be iterated. At run time the same code throws TypeError: settings is not iterable.
Plain objects are not iterable in JavaScript, even though for...in walks their keys. Arrays, strings, Map, Set and the results of Object.entries, Object.keys and Object.values are.
At a glance
| Code | TS2488 |
|---|---|
| Message | Type '{0}' must have a '[Symbol.iterator]()' method that returns an iterator. |
| TypeScript's category | error |
| Listed under | Types do not match |
| Turned on by | No option: every checked file reports it |
| In .js files | Reported when the file is checked: checkJs in jsconfig.json, or // @ts-check at the top of the file |
The message is TypeScript's own text from its diagnostic messages file (Apache-2.0); {0} is filled in by tsc for each error.
What it looks like
const settings = { theme: "dark", language: "en-US" };
for (const [key, value] of settings) {error TS2488: Type '{ theme: string; language: string; }' must have a '[Symbol.iterator]()' method that returns an iterator.
console.log(key, value);
}
const [firstSetting] = settings;error TS2488: Type '{ theme: string; language: string; }' must have a '[Symbol.iterator]()' method that returns an iterator.
export { firstSetting };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} What causes it
- A plain object is used where an iterable is needed, often a settings or lookup object that was an array or a
Mapin an earlier version of the code. - A value that may be
undefinedis destructured, and the message showsundefinedin the type; afor...ofover such a value is reported as TS18048 instead.
How to fix it
Iterate Object.entries, keys or values
for (const [key, value] of Object.entries(settings)) gives each pair, typed as [string, string] here. If the lookup is consulted and changed often, a Map is iterable by itself and keeps insertion order.
const settings = { theme: "dark", language: "en-US" };
for (const [key, value] of Object.entries(settings)) {
console.log(key, value);
}
const [firstSetting] = Object.keys(settings);
const lookup = new Map(Object.entries(settings));
for (const [key, value] of lookup) {
console.log(key, value);
}
export { firstSetting };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Gotchas
Object.entriestypes keys asstring, not as the object's property names, because an object may carry more properties than its type lists.for (const key in settings)is allowed on a plain object, but readingsettings[key]inside it is TS7053, becausekeyis typed asstring.
Sources
- Iteration protocols · MDN Web Docs
- Object.entries() · MDN Web Docs
- diagnosticMessages.json (TypeScript 5.9.3) · TypeScript on GitHub
Last reviewed 20 October 2026.