For AI agents: the complete documentation index is available at https://rslint.rs/llms.txt, the full documentation bundle is available at https://rslint.rs/llms-full.txt, and this page is available as Markdown at https://rslint.rs/rules/unicorn/no-for-each.md.
close

no-for-each

Unreleased

Configuration

PresetConfigured Value
✅ unicornPlugin.configs.recommended"error"
rslint.config.ts
import { defineConfig, unicornPlugin } from '@rslint/core';

export default defineConfig([
  unicornPlugin.configs.recommended,
  {
    rules: {
      'unicorn/no-for-each': 'error',
    },
  },
]);

Prefer for…of over the forEach method.

Using a for…of statement can improve readability, allow early exits with break or return, and allow iterations to be skipped with continue. TypeScript control-flow narrowing also remains in the surrounding function instead of crossing a callback boundary.

A receiver known not to be an array, such as a Map or Set, is ignored. A typed array is reported because for…of iterates it in the same way as TypedArray#forEach(), although the rule only offers an edit when the receiver is definitely an Array, ReadonlyArray, or tuple.

Examples

Incorrect:

array.forEach(element => {
	bar(element);
});

Correct:

for (const element of array) {
	bar(element);
}

Optional arrays are guarded before iteration:

if (array) {
	for (const element of array) {
		bar(element);
	}
}

The callback index becomes the first value from entries():

for (const [index, element] of array.entries()) {
	bar(element, index);
}

Differences from upstream

Rslint withholds the edit when an ordinary function callback's this or arguments is captured by a nested arrow function. Removing the callback boundary would change what those lexical references capture. The diagnostic is still reported; rewrite the loop manually when this pattern is intentional.

The same safety check applies to parameter defaults. Rslint also withholds the edit when the first parameter's binding or default references the index parameter: converting to [index, element] would initialize the index earlier than the original callback. Safe defaults in two-parameter callbacks are retained:

[undefined].forEach((element = 1, index) => use(element, index));
// Autofix:
for (const [index, element = 1] of [undefined].entries()) use(element, index);

Rslint also withholds the edit when the callback declares a var binding. Removing the function boundary would hoist that binding into the surrounding scope, where it could conflict with an outer let or const declaration or leak state between iterations. A var inside a nested function is unaffected.

Further reading