ESLint InterlaceESLint Interlace
Plugin: reliabilityRules

no-await-in-loop

ESLint rule documentation for no-await-in-loop

📡 Live from GitHub — This documentation is fetched directly from no-await-in-loop.md and cached for 6 hours.

Keywords: async, await, loop, Promise.all, concurrency, performance, ESLint rule, sequential, parallel, LLM-optimized

Disallow await inside loops without considering concurrency implications

Disallow await inside loops without considering concurrency implications. This rule is part of eslint-plugin-reliability and provides LLM-optimized error messages with concurrency pattern suggestions.

Quick Summary

AspectDetails
SeverityWarning (performance)
Auto-Fix❌ No (suggests refactoring patterns)
CategoryQuality
ESLint MCP✅ Optimized for ESLint MCP integration
Best ForPerformance-critical applications, API batch operations

Rule Details

Why This Matters

IssueImpactSolution
PerformanceN*latency instead of 1xUse Promise.all()
🔄 Sequential BottleneckBlocks event loopConcurrent execution
📊 ScalabilitySlow with large datasetsBatch processing
🎯 Resource UsageInefficient API callsParallelization

Configuration

OptionTypeDefaultDescription
allowForOfbooleanfalseAllow await in for-of loops
allowWhilebooleanfalseAllow await in while loops
checkConcurrencybooleantrueCheck for potential concurrent execution

Examples

❌ Incorrect

// Sequential execution - N * latency
async function fetchAllUsers(ids: string[]) {
  const users = [];
  for (const id of ids) {
    const user = await fetchUser(id);  // ❌ Await in loop
    users.push(user);
  }
  return users;
}

// Also problematic
async function processItems(items: Item[]) {
  for (let i = 0; i < items.length; i++) {
    await processItem(items[i]);  // ❌ Await in loop
  }
}

✅ Correct

// Concurrent execution - 1x latency (max)
async function fetchAllUsers(ids: string[]) {
  const users = await Promise.all(
    ids.map(id => fetchUser(id))
  );
  return users;
}

// With error handling
async function processItems(items: Item[]) {
  const results = await Promise.allSettled(
    items.map(item => processItem(item))
  );
  return results;
}

// Controlled concurrency with p-map
import pMap from 'p-map';

async function fetchWithLimit(urls: string[]) {
  return pMap(urls, fetchUrl, { concurrency: 5 });
}

Configuration Examples

Basic Usage

{
  rules: {
    'architecture/no-await-in-loop': 'warn'
  }
}

Allow for-of Loops

{
  rules: {
    'architecture/no-await-in-loop': ['warn', {
      allowForOf: true,
      allowWhile: false
    }]
  }
}

Strict Mode

{
  rules: {
    'architecture/no-await-in-loop': ['error', {
      allowForOf: false,
      allowWhile: false,
      checkConcurrency: true
    }]
  }
}

Refactoring Patterns

Sequential to Parallel

// ❌ Before: 10 items = 10 * 100ms = 1000ms
for (const item of items) {
  await process(item);
}

// ✅ After: 10 items = ~100ms (parallel)
await Promise.all(items.map(process));

With Rate Limiting

// Using p-map for controlled concurrency
import pMap from 'p-map';

async function fetchAll(urls: string[]) {
  return pMap(urls, fetch, { concurrency: 3 });
}

When Sequential is Required

// ✅ Extract to separate function if sequential is needed
async function processSequentially(items: Item[]) {
  const results = [];
  for (const item of items) {
    // Dependencies between iterations require sequential
    const result = await processWithPrevious(item, results);
    results.push(result);
  }
  return results;
}

When Not To Use

ScenarioRecommendation
📊 Order-dependent opsUse allowForOf: true or disable rule
🔒 Rate-limited APIsUse controlled concurrency libraries
💾 Memory constraintsSequential may be necessary
🔄 Transaction chainsSequential execution required

Comparison with Alternatives

Featureno-await-in-loopeslint built-inunicorn
Concurrency Check✅ Yes❌ No⚠️ Limited
Loop Type Config✅ Per-type❌ No❌ No
LLM-Optimized✅ Yes❌ No❌ No
ESLint MCP✅ Optimized❌ No❌ No
Pattern Suggestions✅ Yes❌ No⚠️ Limited

Further Reading

Known False Negatives

The following patterns are not detected due to static analysis limitations:

Dynamic Variable References

Why: Static analysis cannot trace values stored in variables or passed through function parameters.

// ❌ NOT DETECTED - Value from variable
const value = externalSource();
processValue(value); // Variable origin not tracked

Mitigation: Implement runtime validation and review code manually. Consider using TypeScript branded types for validated inputs.

Wrapped or Aliased Functions

Why: Custom wrapper functions or aliased methods are not recognized by the rule.

// ❌ NOT DETECTED - Custom wrapper
function myWrapper(data) {
  return internalApi(data); // Wrapper not analyzed
}
myWrapper(unsafeInput);

Mitigation: Apply this rule's principles to wrapper function implementations. Avoid aliasing security-sensitive functions.

Imported Values

Why: When values come from imports, the rule cannot analyze their origin or construction.

// ❌ NOT DETECTED - Value from import
import { getValue } from './helpers';
processValue(getValue()); // Cross-file not tracked

Mitigation: Ensure imported values follow the same constraints. Use TypeScript for type safety.

On this page

No Headings