Skip to content

noEagerArrayMethodsInLoops (JavaScript)

Language JavaScript (and super languages)
biome.json
{
"linter": {
"rules": {
"nursery": {
"noEagerArrayMethodsInLoops": "error"
}
}
}
}

Disallow .map() and .filter() calls in the header of for...of loops.

Calling .map() or .filter() on an array in the header of a for...of or for await...of loop goes through the items twice. The method visits every item to build a new array, and then the loop visits every item of that new array. Doing the same work inside the loop body avoids the extra array and the second pass.

The rule only checks calls written directly in the loop header. It doesn’t report an array that is stored in a variable before the loop.

The rule ignores calls on iterators. An iterator is an object that produces its values one at a time, so its .map() and .filter() methods don’t build an array. The rule treats the following as iterators:

  • The result of .values(), .keys(), or .entries() called without arguments, or of .matchAll().
  • The result of Iterator.from(), Iterator.concat(), Iterator.zip(), or Iterator.zipKeyed().
  • The result of calling .map(), .filter(), .flatMap(), .take(), or .drop() on an iterator.
  • The result of calling a generator function declared in the same file.
  • A const variable that is assigned one of the values above.
  • In TypeScript, a value whose type annotation or type assertion names a built-in iterator type, such as Iterator<string> or Generator<number>, or a type alias for one. This includes the result of calling a function declared in the same file with such a return type.
for (const name of users.map((user) => user.name)) {
console.log(name);
}
code-block.js:1:20 lint/nursery/noEagerArrayMethodsInLoops ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

⚠ This .map() call builds a new array that is only used by the for...of loop.

> 1 │ for (const name of users.map((user) => user.name)) {
│ ^^^^^^^^^
2 │ console.log(name);
3 │ }

ℹ .map() visits every item to build the array before the loop starts, and the loop then visits every item again. The array is discarded after the loop.

ℹ Loop over the original items and do the callback's work inside the loop body.

ℹ This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.

for (const user of users.filter((user) => user.isActive)) {
console.log(user);
}
code-block.js:1:20 lint/nursery/noEagerArrayMethodsInLoops ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

⚠ This .filter() call builds a new array that is only used by the for...of loop.

> 1 │ for (const user of users.filter((user) => user.isActive)) {
│ ^^^^^^^^^^^^
2 │ console.log(user);
3 │ }

ℹ .filter() visits every item to build the array before the loop starts, and the loop then visits every item again. The array is discarded after the loop.

ℹ Loop over the original items and skip the unwanted ones inside the loop body, for example with an if statement.

ℹ This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.

for (const user of users) {
console.log(user.name);
}
for (const user of users) {
if (user.isActive) {
console.log(user);
}
}
// `.values()` returns an iterator, so `.map()` doesn't build an array.
for (const name of users.values().map((user) => user.name)) {
console.log(name);
}
// The array is created before the loop.
const names = users.map((user) => user.name);
for (const name of names) {
console.log(name);
}