How to fix 'downlevelIteration' is required for iterating over complex types in TypeScript

TypeScriptINTERMEDIATEMEDIUMUpdated Dec 8, 2025· 174 views

This TypeScript error occurs when you try to use modern iteration features (for...of loops, array spread, or destructuring) on complex types while targeting older JavaScript environments (ES3/ES5) without enabling the downlevelIteration compiler option. The fix involves enabling downlevelIteration in tsconfig.json or upgrading your compilation target.

Quick fix

Fixes 'downlevelIteration' is required for iterating over complex types

{
  "compilerOptions": {
    "target": "ES5",
    "downlevelIteration": true
  }
}
  1. Enable downlevelIteration in tsconfig.json
  2. Upgrade your compilation target to ES6 or higher
  3. Use Array.from() instead of spread syntax
  4. Refactor for...of loops to traditional for loops
typescript — error
{
"compilerOptions": {
"target": "ES5",
"downlevelIteration": true
}
}
'downlevelIteration' is required for iterating over complex types

What this error means

The 'downlevelIteration' error appears when TypeScript needs to transpile modern JavaScript iteration features to older ECMAScript versions (ES3 or ES5), but encounters types that require more sophisticated iteration handling. "Downleveling" is TypeScript's term for transpiling to an older version of JavaScript. When targeting ES5 or ES3, TypeScript must convert modern iteration primitives—such as for...of loops, array spread syntax ([...array]), argument spread (fn(...args)), and destructuring—into code that works in environments without native Symbol.iterator support. By default, TypeScript uses a simplified downleveling approach that works for basic arrays but fails for: - Iterators and iterables (Map.values(), Set.entries(), generators) - Strings in for...of loops (which iterate by Unicode code point) - Custom objects implementing Symbol.iterator - Type unions that may include iterables When you enable downlevelIteration, TypeScript emits more accurate helper code that checks for Symbol.iterator at runtime and properly handles all iteration scenarios. This comes at the cost of slightly larger bundle sizes due to the additional helper functions.

Typical symptoms

  • Compiler error: 'Type X is not an array type or a string type. Use compiler option --downlevelIteration to allow iterating of iterators'
  • Error when using spread operator on Maps, Sets, or custom iterables
  • for...of loop fails to compile when iterating over complex types
  • Array.from() works but spread syntax [...iterable] does not
  • Code works fine when targeting ES6+ but breaks with ES5 target
  • Error when destructuring iterators or using rest parameters with iterables

Common causes

  1. Using for...of loop on iterables (Map, Set, generators) while targeting ES5/ES3
  2. Spreading non-array iterables with [...iterable] syntax in ES5 mode
  3. TypeScript target is set to 'ES3' or 'ES5' without downlevelIteration enabled
  4. Iterating over strings with for...of which requires Symbol.iterator
  5. Using argument spread (...args) with iterators in older JS targets
  6. Destructuring values from generators or custom iterables
  7. Attempting to spread Map.values(), Set.entries(), or similar iterator methods

How to fix "'downlevelIteration' is required for iterating over complex types"

1Enable downlevelIteration in tsconfig.json

The most direct fix is to enable the downlevelIteration compiler option:

json
{
  "compilerOptions": {
    "target": "ES5",
    "downlevelIteration": true
  }
}

This tells TypeScript to emit more sophisticated iteration code that properly handles all iterable types. After making this change, recompile your project:

bash
npx tsc
# or
npm run build

The error should now be resolved. TypeScript will generate helper functions that check for Symbol.iterator and handle iteration correctly for all types.

2Upgrade your compilation target to ES6 or higher

If you don't need to support very old browsers (IE11 and earlier), consider upgrading your target:

json
{
  "compilerOptions": {
    "target": "ES2015",  // or "ES6", "ES2016", etc.
    "module": "commonjs"
  }
}

Modern JavaScript environments (ES2015+) have native Symbol.iterator support, so TypeScript doesn't need to downlevel iteration at all. This produces:
- Smaller bundle sizes (no helper functions)
- Faster code (native iteration)
- Simpler output code

Check your browser/runtime requirements before changing:

bash
# ES2015/ES6 supported by:
# - Node.js 6+
# - Chrome 51+
# - Firefox 54+
# - Safari 10+
# - Edge 15+
3Use Array.from() instead of spread syntax

If you can't enable downlevelIteration or upgrade your target, refactor code to use Array.from():

typescript
// BEFORE - causes error with ES5 target
const mySet = new Set([1, 2, 3]);
const array = [...mySet];  // Error: downlevelIteration required

// AFTER - works without downlevelIteration
const array = Array.from(mySet);

This works because Array.from() is a runtime function (can be polyfilled) rather than syntax that needs transpilation. More examples:

typescript
// Map values
const myMap = new Map([["a", 1], ["b", 2]]);
// const values = [...myMap.values()];  // Error
const values = Array.from(myMap.values());  // Works

// Strings (iterate by character)
const text = "hello";
// const chars = [...text];  // Error with for...of on strings
const chars = Array.from(text);  // Works

// Custom iterables
function* generator() {
  yield 1;
  yield 2;
}
// const items = [...generator()];  // Error
const items = Array.from(generator());  // Works

Note: You'll still need Array.from polyfilled for ES3/ES5 environments.

4Refactor for...of loops to traditional for loops

Replace for...of loops with traditional index-based loops:

typescript
// BEFORE - requires downlevelIteration
const mySet = new Set(['a', 'b', 'c']);
for (const item of mySet) {
  console.log(item);
}

// AFTER - works without downlevelIteration
const items = Array.from(mySet);
for (let i = 0; i < items.length; i++) {
  console.log(items[i]);
}

// Or use forEach (if available)
Array.from(mySet).forEach(item => {
  console.log(item);
});

For Maps:

typescript
// BEFORE
const myMap = new Map([["key1", "val1"], ["key2", "val2"]]);
for (const [key, value] of myMap) {
  console.log(key, value);
}

// AFTER
myMap.forEach((value, key) => {
  console.log(key, value);
});

This approach requires no special compiler flags and works in all environments.

5Enable importHelpers to reduce bundle size

If you enable downlevelIteration, also enable importHelpers to avoid duplicating helper code:

json
{
  "compilerOptions": {
    "target": "ES5",
    "downlevelIteration": true,
    "importHelpers": true
  }
}

Install the tslib package which contains the helper functions:

bash
npm install tslib

This makes TypeScript import iteration helpers from tslib instead of inlining them in every file:

typescript
// Without importHelpers - helpers inlined in every file (larger bundle)
var __read = function() { /* helper code */ };
var array = __read(mySet);

// With importHelpers - single shared import (smaller bundle)
import { __read } from "tslib";
var array = __read(mySet);

For large projects, this can significantly reduce bundle size.

Advanced notes

### How downlevelIteration Works Internally

When downlevelIteration is enabled, TypeScript generates helper functions that implement the ES6 iteration protocol for ES5:

Without downlevelIteration (simple array loop):

typescript
// Source
const items = [...myArray];

// Compiled to ES5 (simple, only works for arrays)
var items = myArray.slice();

With downlevelIteration (proper iteration protocol):

typescript
// Source
const items = [...myIterable];

// Compiled to ES5 (checks for Symbol.iterator)
var __read = function(o, n) {
  var m = typeof Symbol === "function" && o[Symbol.iterator];
  if (!m) return o;
  var i = m.call(o), r, ar = [], e;
  try {
    while ((n === void 0 || n-- > 0) && !(r = i.next()).done)
      ar.push(r.value);
  } catch (error) { e = { error: error }; }
  finally {
    try {
      if (r && !r.done && (m = i["return"])) m.call(i);
    } finally { if (e) throw e.error; }
  }
  return ar;
};

var items = __read(myIterable);

The helper checks for Symbol.iterator and uses it if available, falling back to array-like behavior otherwise.

### Spread Operator Behavioral Differences

An important distinction when using spread syntax with downlevelIteration:

typescript
const sparseArray = [1, , 3];  // Array with hole at index 1

// Without downlevelIteration (uses .concat or .slice)
const copy1 = [...sparseArray];  // [1, empty, 3] - preserves hole

// With downlevelIteration (uses iterator)
const copy2 = [...sparseArray];  // [1, undefined, 3] - fills holes

The spread syntax with downlevelIteration replaces holes with undefined because the iterator protocol doesn't distinguish between undefined values and holes.

### Performance Considerations

Enabling downlevelIteration has tradeoffs:

Bundle Size Impact:
- Helper functions add ~300-500 bytes per file without importHelpers
- With importHelpers + tslib: ~2KB one-time cost
- For small projects: May increase bundle by 5-10%
- For large projects with importHelpers: <1% increase

Runtime Performance:
- Native iteration (ES6+ target): Fastest
- Simple downleveling (ES5 without flag): Fast for arrays
- downlevelIteration (ES5 with flag): Slower due to protocol checks

Recommendation:
- If targeting modern environments: Use ES2015+ target (no downleveling needed)
- If targeting IE11: Enable downlevelIteration + importHelpers
- If bundle size is critical: Refactor to Array.from() instead

### When Symbol.iterator is Missing

If your runtime doesn't have Symbol.iterator (very old browsers), you'll need a polyfill even with downlevelIteration:

bash
npm install core-js
typescript
// At entry point
import 'core-js/es/symbol';
import 'core-js/es/array/iterator';
import 'core-js/es/map';
import 'core-js/es/set';

Or use a comprehensive polyfill service:

html
<script src="https://polyfill.io/v3/polyfill.min.js?features=Symbol%2CArray.prototype.iterator"></script>

### Library Authorship

If you're writing a library, consider your consumers' needs:

Option 1: Publish ES5 with downlevelIteration
- Widest compatibility
- Consumers don't need special config
- Larger bundle size

Option 2: Publish ES2015+ modules
- Let consumers choose their target
- Smaller published code
- Requires consumers to transpile node_modules (not always configured)

Best practice: Publish both formats:

json
// package.json
{
  "main": "dist/index.js",           // ES5 + downlevelIteration
  "module": "dist/index.esm.js",     // ES2015 modules
  "types": "dist/index.d.ts"
}

### TypeScript Version Notes

- TypeScript 2.3+ required for downlevelIteration
- Before TypeScript 2.3, no option existed (had to use ES6 target)
- Modern projects (TypeScript 4+) should consider ES2020+ targets instead

Frequently asked questions

What does "'downlevelIteration' is required for iterating over complex types" mean?
The 'downlevelIteration' error appears when TypeScript needs to transpile modern JavaScript iteration features to older ECMAScript versions (ES3 or ES5), but encounters types that require more sophisticated iteration handling. "Downleveling" is TypeScript's term for transpiling to an older version of JavaScript. iterator support.
How do I fix "'downlevelIteration' is required for iterating over complex types"?
This TypeScript error occurs when you try to use modern iteration features (for...of loops, array spread, or destructuring) on complex types while targeting older JavaScript environments (ES3/ES5) without enabling the downlevelIteration compiler option. The fix involves enabling downlevelIteration in tsconfig.json or upgrading your compilation target.
What causes "'downlevelIteration' is required for iterating over complex types"?
Using for...of loop on iterables (Map, Set, generators) while targeting ES5/ES3 Spreading non-array iterables with [...iterable] syntax in ES5 mode TypeScript target is set to 'ES3' or 'ES5' without downlevelIteration enabled
Is the "'downlevelIteration' is required for iterating over complex types" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading