ESLint InterlaceESLint Interlace
Plugin: maintainabilityRules

cognitive-complexity

ESLint rule documentation for cognitive-complexity

📡 Live from GitHub — This documentation is fetched directly from cognitive-complexity.md and cached for 6 hours.

Keywords: cognitive complexity, code complexity, SonarQube, ESLint rule, code maintainability, refactoring, code quality, auto-fix, LLM-optimized

Enforces a maximum cognitive complexity threshold with refactoring guidance

Enforces a maximum cognitive complexity threshold with refactoring guidance. This rule is part of eslint-plugin-maintainability and provides LLM-optimized error messages with fix suggestions.

💡 Provides suggestions | ⚠️ Set to warn in recommended

Quick Summary

AspectDetails
SeverityWarning (code quality)
Auto-Fix⚠️ Suggests fixes (manual application)
CategoryQuality
ESLint MCP✅ Optimized for ESLint MCP integration
Best ForAll projects, especially large codebases requiring maintainability

Rule Details

Cognitive complexity is a measure of how difficult code is to understand. Unlike cyclomatic complexity, cognitive complexity takes into account nested structures and breaks that interrupt the linear flow of code.

Complexity Factors

FactorWeightExample
Conditionals+1if, else if, ? :, ??
Loops+1for, while, do-while
Switches+1 per caseswitch statement cases
Nesting+nesting levelNested if inside for
Logical Operators+1&&, || (sequence breaks)
Catch blocks+1catch in try-catch
Recursion+1Function calling itself

Error Message Format

The rule provides LLM-optimized error messages that include actionable guidance:

⚡ Cognitive Complexity: 23/15 (8 over) | Function: processOrder(order) | src/orders.ts:45
📊 Breakdown: 7 conditionals, 3 loops, 4 max nesting
💡 Recommended Pattern: Extract Method + Guard Clauses
🔧 Refactoring Steps:
   1. Extract nested blocks into helper functions
   2. Replace nested if/else with guard clauses (early returns)
   3. Apply Extract Method + Guard Clauses to reduce branching logic
   4. Target complexity: 15 or lower
⏱️  Estimated effort: 24 minutes

Message Components

ComponentPurposeExample
Complexity ScoreShows current/max and overage23/15 (8 over)
Function NameIdentifies problematic functionprocessOrder(order)
LocationFile path and line numbersrc/orders.ts:45
BreakdownDetailed complexity factors7 conditionals, 3 loops, 4 max nesting
PatternRecommended refactoring patternExtract Method + Guard Clauses
StepsConcrete refactoring actionsNumbered list of steps
EffortTime estimate for refactoring24 minutes

This format is optimized for:

  • 🤖 LLMs - Can parse and act on the structured guidance
  • 👨‍💻 Developers - Clear, actionable information
  • 📊 Planning - Time estimates for task estimation

Configuration

OptionTypeDefaultDescription
maxComplexitynumber15Maximum allowed cognitive complexity
includeMetricsbooleantrueInclude detailed complexity breakdown in messages

Examples

❌ Incorrect

// Cognitive complexity: 23 (too high!)
function processOrder(order: Order) {
  if (order.status === 'pending') {
    // +1
    if (order.items.length > 0) {
      // +2 (nested)
      for (const item of order.items) {
        // +3 (nested)
        if (item.quantity > 0) {
          // +4 (nested)
          if (item.price > 100) {
            // +5 (nested)
            if (item.discount && item.discount > 0) {
              // +6 (nested) +1 (&&)
              // Process high-value discounted item
            } else {
              // +1
              // Process high-value regular item
            }
          } else {
            // +1
            // Process low-value item
          }
        }
      }
    } else {
      // +1
      throw new Error('Empty order');
    }
  } else if (order.status === 'cancelled') {
    // +1
    // Handle cancellation
  }
}

✅ Correct

// Cognitive complexity: 5 (much better!)
function processOrder(order: Order) {
  validateOrder(order); // +0 (extracted)

  if (order.status === 'pending') {
    // +1
    processItems(order.items);
  } else if (order.status === 'cancelled') {
    // +1
    handleCancellation(order);
  }
}

function validateOrder(order: Order) {
  // Complexity: 2
  if (order.items.length === 0) {
    // +1
    throw new Error('Empty order');
  }
}

function processItems(items: OrderItem[]) {
  // Complexity: 4
  for (const item of items) {
    // +1
    if (item.quantity > 0) {
      // +2 (nested)
      processValidItem(item);
    }
  }
}

function processValidItem(item: OrderItem) {
  // Complexity: 3
  if (item.price > 100) {
    // +1
    processHighValueItem(item);
  } else {
    // +1
    processLowValueItem(item);
  }
}

Configuration Examples

Basic Usage

{
  rules: {
    'maintainability/cognitive-complexity': ['warn', {
      maxComplexity: 15
    }]
  }
}

Strict Mode

{
  rules: {
    'maintainability/cognitive-complexity': ['error', {
      maxComplexity: 10,
      includeMetrics: true
    }]
  }
}

Relaxed Mode (Legacy Code)

{
  rules: {
    'maintainability/cognitive-complexity': ['warn', {
      maxComplexity: 25,
      includeMetrics: false  // Less verbose for gradual refactoring
    }]
  }
}

Refactoring Strategies

1. Extract Method

// Before: Complexity 18
function validateUser(user: User) {
  if (user.email) {
    if (user.email.includes('@')) {
      if (user.email.length > 5) {
        // More validation...
      }
    }
  }
}

// After: Complexity 5
function validateUser(user: User) {
  if (user.email) {
    validateEmail(user.email);
  }
}

function validateEmail(email: string) {
  if (!email.includes('@')) return false;
  if (email.length <= 5) return false;
  return true;
}

2. Use Guard Clauses

// Before: Complexity 12
function processPayment(payment: Payment) {
  if (payment.amount > 0) {
    if (payment.method === 'credit') {
      if (payment.card) {
        // Process credit card
      }
    }
  }
}

// After: Complexity 3
function processPayment(payment: Payment) {
  if (payment.amount <= 0) return;
  if (payment.method !== 'credit') return;
  if (!payment.card) return;

  // Process credit card
}

3. Strategy Pattern

// Before: Complexity 15
function calculateShipping(order: Order) {
  if (order.type === 'express') {
    if (order.weight > 10) {
      return order.weight * 5;
    } else {
      return 20;
    }
  } else if (order.type === 'standard') {
    // More conditions...
  }
}

// After: Complexity 2
const shippingStrategies = {
  express: (order: Order) => order.weight > 10 ? order.weight * 5 : 20,
  standard: (order: Order) => /* ... */,
};

function calculateShipping(order: Order) {
  const strategy = shippingStrategies[order.type];
  return strategy ? strategy(order) : 0;
}

Why This Matters

IssueImpactSolution
🧠 MaintainabilityHard to understand and modifyExtract methods
🐛 Bug DensityMore bugs in complex functionsSimplify logic
⏱️ Review TimeTakes longer to review complex codeBreak into smaller pieces
📝 TestingHarder to write comprehensive testsReduce branching
🔄 RefactoringRisky to change complex functionsUse design patterns

Comparison with Cyclomatic Complexity

AspectCognitive ComplexityCyclomatic Complexity
Nesting Impact✅ Penalizes deeply nested❌ Treats all equal
Linear Flow✅ Recognizes clarity❌ Ignores flow
Readability Focus✅ Human-centric❌ Branch-centric
Better forUnderstanding codeTest coverage

Comparison with Alternatives

Featurecognitive-complexityeslint-plugin-complexitySonarQube
Cognitive Complexity✅ Yes❌ Cyclomatic only✅ Yes
LLM-Optimized✅ Yes❌ No❌ No
ESLint MCP✅ Optimized❌ No❌ No
Fix Suggestions✅ Detailed⚠️ Basic⚠️ Basic
ESLint Integration✅ Native✅ Native❌ External

Further Reading

References

Inspired by SonarQube RSPEC-3776

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.

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