Comprehensive guide to all performance issue detectors in Code Evolution Lab.

Overview

Code Evolution Lab includes 11 detectors that analyze AST to find performance issues. All 11 run on every tier — there is no detector-level gating between Starter and Pro (see Pricing Tiers).

Category Detector Severity
Database N+1 Query HIGH
Database Missing Index MEDIUM
Performance Inefficient Loop MEDIUM-HIGH
Performance Blocking I/O HIGH
Memory Memory Leak CRITICAL
Memory Resource Leaks HIGH
Network Large Payload MEDIUM
Network Missing Caching MEDIUM
Security ReDoS Vulnerability HIGH
Frontend Bundle Size MEDIUM
Frontend DOM Manipulation MEDIUM

File paths on this page refer to the backend implementation, which powers web and API scans. The CLI and GitHub Action run a separate implementation of the same 11 categories in packages/core-engine/src/rules/.

Detectors

N+1 Query Detector

File: backend/src/detectors/n1-query-detector.ts

What It Does: The N+1 Query Detector identifies one of the most common and devastating performance issues in database-driven applications: executing separate database queries inside loops. This pattern causes exponential growth in database load as your data scales.

Why It Matters: When you fetch a list of N items and then query the database once for each item, you execute N+1 total queries (1 for the list + N individual queries). With 100 items, this becomes 101 queries instead of 1-2 optimized queries. At scale, this can:

How It Works: The detector performs AST traversal to identify loops (for, for-of, forEach, map, etc.) containing database operations. It recognizes query patterns from major ORMs:

The detector tracks query depth (nested loops increase severity) and counts multiple query calls to calculate accurate impact scores.

Detection Patterns:

Example Issue:

// ❌ N+1 Query - executes N database queries
async function getOrdersWithUsers() {
  const orders = await Order.findAll();
  for (const order of orders) {
    const user = await User.findByPk(order.userId); // Query in loop!
    order.user = user;
  }
  return orders;
}

Severity Calculation:

Impact Metrics:

{
  category: 'performance',
  potentialQueryReduction: 'N queries → 1 query',
  fixDifficulty: 'moderate'
}

Inefficient Loop Detector

File: backend/src/detectors/inefficient-loop-detector.ts

What It Does: The Inefficient Loop Detector is the most comprehensive detector, analyzing 12 distinct anti-patterns that cause performance degradation in iterative code. Loops are execution hotspots—optimizing them provides immediate, measurable performance gains.

Why It Matters: Loops execute repeatedly, so even minor inefficiencies compound dramatically:

How It Works: The detector uses multi-pass AST analysis:

  1. First Pass: Identifies all loop constructs and their nesting depth
  2. Second Pass: Scans loop bodies for problematic patterns
  3. Context Analysis: Checks if operations can be hoisted, parallelized, or optimized
  4. Framework Awareness: Recognizes React/Vue/Angular patterns to avoid false positives

It detects complexity by analyzing call graphs, tracking variable usage across loop iterations, and measuring cognitive complexity of nested structures.

Detection Patterns:

Pattern Severity Description
await in loop HIGH Sequential async instead of parallel
Nested loops MEDIUM-HIGH O(n²) or O(n³) complexity
Array method chaining MEDIUM .filter().map() multiple iterations
Nested array methods HIGH O(n²) from nested .map()/.filter()
Array.push in loop LOW Inefficient array building
DOM manipulation HIGH Layout thrashing
String concatenation MEDIUM Use Array.join() instead
Regex compilation MEDIUM Compile regex outside loop
JSON operations MEDIUM Avoid JSON.parse/stringify in loop
Sync file I/O CRITICAL Blocking operations
Array.includes/indexOf MEDIUM O(n²) → use Set/Map
Object.keys() lookups MEDIUM Inefficient object iteration

Example Issue:

// ❌ await in loop - sequential execution
async function processItems(items) {
  const results = [];
  for (const item of items) {
    const result = await processItem(item); // Sequential!
    results.push(result);
  }
  return results;
}

// ✅ Suggested fix - parallel execution
async function processItems(items) {
  return Promise.all(items.map(item => processItem(item)));
}

Memory Leak Detector

File: backend/src/detectors/memory-leak-detector.ts

What It Does: The Memory Leak Detector identifies patterns where resources are allocated but never released, causing memory to grow unbounded over time. It’s framework-aware, understanding React, Vue, and Angular lifecycle patterns to catch cleanup violations.

Why It Matters: Memory leaks are insidious—they don’t crash immediately but degrade performance over hours or days:

How It Works: The detector performs sophisticated lifecycle analysis:

  1. Import Detection: Scans imports to identify the framework (React useEffect, Vue onMounted, Angular OnDestroy)
  2. Lifecycle Tracking: Maps resource allocations to their expected cleanup locations
  3. Cleanup Verification: Checks if allocated resources have corresponding cleanup calls
  4. Control Flow Analysis: Ensures cleanup happens on all code paths (not just happy path)
  5. Dependency Analysis: For React, validates that cleanup dependencies match effect dependencies

It tracks event listeners, timers, subscriptions, WebSocket connections, and animation frames across component lifecycles.

Detection Patterns:

Type Severity Detection
Event listeners HIGH addEventListener without removeEventListener
Timers HIGH setInterval/setTimeout without cleanup
Global variables MEDIUM window.x / global.x assignments
Closures MEDIUM Closures capturing large data
React effects HIGH useEffect without cleanup
Vue lifecycle HIGH Missing unmounted cleanup
Angular lifecycle HIGH Missing ngOnDestroy cleanup

Framework Detection:

// Detects framework from imports
import { useEffect } from 'react';  // → React context
import { onMounted } from 'vue';    // → Vue context
import { OnDestroy } from '@angular/core';  // → Angular context

Example Issue:

// ❌ React useEffect without cleanup
useEffect(() => {
  const handler = () => console.log('resize');
  window.addEventListener('resize', handler);
  // Missing cleanup!
}, []);

// ✅ With cleanup
useEffect(() => {
  const handler = () => console.log('resize');
  window.addEventListener('resize', handler);
  return () => window.removeEventListener('resize', handler);
}, []);

Large Payload Detector

File: backend/src/detectors/large-payload-detector.ts

What It Does: The Large Payload Detector identifies database queries and API endpoints that return unbounded or excessive data. These “data bombs” consume bandwidth, overwhelm clients, and slow down your entire application stack.

Why It Matters: Returning too much data creates cascading problems:

Real example: An unbounded /users endpoint returning 50,000 users as JSON (20MB) instead of paginated results.

How It Works: The detector performs data-flow analysis:

  1. Query Detection: Identifies database calls (findAll, find, raw SQL)
  2. Pagination Check: Scans for limit, take, skip, offset parameters
  3. Response Tracking: Follows variables from DB to API response (res.json, res.send)
  4. Column Analysis: Detects SELECT * vs explicit field selection
  5. Streaming Detection: Recognizes streaming responses as safe (not loaded into memory)

It understands common pagination patterns: withPagination(), cursor-based pagination, and offset/limit pagination.

Detection Patterns:

Pattern Severity Description
SELECT * queries MEDIUM Fetching all columns
Missing pagination HIGH Unbounded result sets
No field selection MEDIUM Returning full objects
Large array returns MEDIUM Arrays without limits

Data-Flow Analysis:

Example Issue:

// ❌ No pagination, returns all users
app.get('/users', async (req, res) => {
  const users = await User.findAll();  // Unbounded!
  res.json(users);
});

// ✅ With pagination
app.get('/users', async (req, res) => {
  const { page = 1, limit = 20 } = req.query;
  const users = await User.findAll({
    limit,
    offset: (page - 1) * limit
  });
  res.json(users);
});

Missing Index Detector

File: backend/src/detectors/missing-index-detector.ts

What It Does: The Missing Index Detector analyzes database queries to identify columns that would benefit from indexing. Unindexed queries force full table scans, which become exponentially slower as data grows.

Why It Matters: Missing indexes are the #1 cause of database performance degradation in production:

How It Works: The detector performs query analysis:

  1. ORM Query Parsing: Extracts WHERE conditions from Prisma where, Sequelize findAll, Mongoose find
  2. Raw SQL Parsing: Analyzes SQL strings for WHERE, ORDER BY, JOIN clauses
  3. Column Extraction: Identifies columns used in filter/sort operations
  4. Index Simulation: Estimates if current query pattern would use an index
  5. Recommendation: Suggests composite indexes for multi-column queries

Detection Patterns:


Blocking I/O Detector

File: backend/src/detectors/blocking-io-detector.ts

What It Does: The Blocking I/O Detector identifies synchronous operations that freeze Node.js’s event loop, preventing the server from handling other requests. Even a single blocking operation can halt your entire application.

Why It Matters: Node.js is single-threaded—blocking operations stall everything:

Real scenario: A dashboard loading config with readFileSync blocked the entire API, causing all endpoints to timeout during traffic spikes.

How It Works: The detector scans for synchronous Node.js APIs:

  1. File System: fs.readFileSync, fs.writeFileSync, fs.statSync, etc.
  2. Child Processes: execSync, spawnSync, child_process blocking APIs
  3. Crypto: pbkdf2Sync, scryptSync, randomBytes without callback
  4. HTTP: Synchronous request libraries (rare but exists)
  5. Context Analysis: Flags severity as CRITICAL if found in request handlers

Detection Patterns:


ReDoS Vulnerability Detector

File: backend/src/detectors/redos-detector.ts

What It Does: The ReDoS (Regular Expression Denial of Service) Detector identifies regex patterns vulnerable to catastrophic backtracking. A malicious input can cause these regexes to run for seconds, minutes, or even hang indefinitely.

Why It Matters: ReDoS is a critical security vulnerability:

How It Works: The detector performs regex complexity analysis:

  1. Pattern Extraction: Finds all regex literals and new RegExp() calls
  2. Quantifier Analysis: Identifies nested/overlapping quantifiers ((a+)+, (a*)*, (a|a)+)
  3. Backtracking Simulation: Estimates worst-case time complexity
  4. Input Tracking: Checks if regex is applied to user input (higher risk)
  5. Complexity Scoring: Calculates ReDoS risk score based on nesting depth

Vulnerable Patterns:

Detection Patterns:


Bundle Size Detector

File: backend/src/detectors/bundle-size-detector.ts

What It Does: The Bundle Size Detector identifies import patterns that bloat your JavaScript bundle, increasing initial page load time and bandwidth costs. It catches common mistakes like importing entire libraries when only small utilities are needed.

Why It Matters: Bundle size directly impacts user experience and SEO:

Real example: A React app importing full lodash + moment unnecessarily added 150KB, increasing load time from 2s to 4.5s.

How It Works: The detector analyzes import statements:

  1. Import Pattern Detection: Identifies namespace imports (import * as _), default imports, named imports
  2. Package Database: Maintains known heavy packages and their tree-shakeable alternatives
  3. Usage Analysis: Checks if imported items are actually used in the code
  4. Duplication Detection: Finds multiple subpath imports from same package
  5. Alternative Suggestion: Recommends lighter alternatives (e.g., date-fns instead of moment)

Detection Patterns:


DOM Manipulation Detector

File: backend/src/detectors/dom-manipulation-detector.ts

What It Does: The DOM Manipulation Detector identifies patterns that cause layout thrashing and UI freezes. The DOM is one of the slowest APIs in browsers—inefficient manipulation creates visible jank.

Why It Matters: Browser rendering is expensive and synchronous:

How It Works: The detector tracks DOM operation patterns:

  1. Property Access Tracking: Identifies layout-triggering properties (offsetWidth, clientHeight, scrollTop)
  2. Operation Sequencing: Detects interleaved read/write patterns
  3. Loop Context: Flags DOM operations inside loops
  4. XSS Analysis: Identifies innerHTML with untrusted data
  5. querySelector Overhead: Detects repeated queries for the same selector

Detection Patterns:


Missing Caching Detector

File: backend/src/detectors/missing-caching-detector.ts

What It Does: The Missing Caching Detector identifies expensive operations that are repeated with identical inputs—prime candidates for caching. Proper caching can reduce response times from seconds to milliseconds.

Why It Matters: Repetitive expensive operations waste resources:

Real scenario: A dashboard recalculating user statistics on every page load (500ms) instead of caching for 5 minutes.

How It Works: The detector performs call-site analysis:

  1. Expensive Operation Detection: Identifies API calls, DB queries, complex computations
  2. Call Frequency Analysis: Tracks if the same call happens multiple times
  3. Input Stability: Checks if function inputs are deterministic/stable
  4. Pure Function Detection: Identifies pure functions suitable for memoization
  5. Framework Integration: Recognizes React useMemo, Vue computed, caching libraries

Detection Patterns:


Resource Leaks Detector

File: backend/src/detectors/resource-leaks-detector.ts

What It Does: The Resource Leaks Detector identifies system resources (connections, files, streams) that are opened but never closed. These leaks exhaust connection pools and file descriptors, causing production crashes.

Why It Matters: Resource exhaustion causes catastrophic failures:

Real incident: A production API exhausted all 100 DB connections in 30 minutes due to a single unclosed connection in an error path.

How It Works: The detector performs resource lifecycle tracking:

  1. Acquisition Detection: Identifies resource creation (createConnection, createReadStream, open)
  2. Cleanup Tracking: Searches for corresponding close/release calls
  3. Control Flow Analysis: Ensures cleanup happens on all paths (success, error, early return)
  4. Try-Finally Verification: Checks if cleanup is in finally blocks
  5. Async Cleanup: Validates async resources are awaited before closing

Detection Patterns:


Base Detector Class

All detectors extend BaseDetector:

abstract class BaseDetector {
  abstract name: string;
  protected issues: Issue[] = [];

  abstract detect(ast: any, context: AnalysisContext): Promise<DetectorResult>;

  protected createIssue(
    type: string,
    severity: 'critical' | 'high' | 'medium' | 'low',
    context: AnalysisContext,
    lineNumber: number,
    title: string,
    description: string,
    codeBefore: string,
    codeAfter?: string,
    estimatedImpact?: EstimatedImpact
  ): Issue;

  protected createImpact(
    severityScore: number,
    description: string,
    confidenceScore: number,
    category: 'performance' | 'memory' | 'network' | 'complexity',
    fixDifficulty: 'trivial' | 'easy' | 'moderate' | 'complex',
    metrics: Record<string, any>
  ): EstimatedImpact;

  protected getCode(node: any, sourceCode: string): string;
}

Creating a Custom Detector

import { BaseDetector } from './base-detector';
import { AnalysisContext, DetectorResult, Issue } from '../types';
import traverse from '@babel/traverse';

export class MyCustomDetector extends BaseDetector {
  name = 'My Custom Detector';

  async detect(ast: any, context: AnalysisContext): Promise<DetectorResult> {
    this.reset();
    const issues: Issue[] = [];

    traverse(ast, {
      CallExpression: (path) => {
        // Your detection logic here
        if (this.isProblematic(path.node)) {
          issues.push(this.createIssue(
            'my_custom_issue',
            'medium',
            context,
            path.node.loc?.start.line || 0,
            'Custom Issue Detected',
            'Description of the issue',
            this.getCode(path.node, context.sourceCode),
            '// Suggested fix',
            this.createImpact(50, 'Impact description', 0.8, 'performance', 'easy', {})
          ));
        }
      }
    });

    return { issues, detectorName: this.name };
  }

  private isProblematic(node: any): boolean {
    // Your logic
    return false;
  }
}

Registering a Custom Detector

In backend/src/analyzer/code-analyzer.ts, add the detector to the record in initializeDetectors() under a new slug:

import { MyCustomDetector } from '../detectors/my-custom-detector';

// Inside initializeDetectors()
const all: Record<string, any> = {
  // ... existing detectors
  'my-custom': new MyCustomDetector(),
};

Also add a matching entry to DETECTOR_REGISTRY so the slug can be selected per scan. To attach a detector to an existing analyzer instance instead:

analyzer.addDetector(new MyCustomDetector());

Detector Configuration

Detectors can be configured via .codeevolutionrc.json. The loader also accepts .codeevolutionrc and codeevolution.config.json, searching upward from the working directory.

This applies to the backend engine only. The CLI has no config file support — it is configured entirely through command-line options.

Each detector is keyed by name, with its own options. Note that these config keys (n1-query, inefficient-loop, memory-leak, large-payload) are not the same as the detector slugs used by the scan API’s detector picker (n1, loop, memory, payload).

{
  "ignore": [
    "**/test/**",
    "**/fixtures/**"
  ],
  "detectors": {
    "n1-query": {
      "enabled": true,
      "severity": "high"
    },
    "memory-leak": {
      "enabled": false
    },
    "large-payload": {
      "enabled": true,
      "customPatterns": ["fetchAllData", "loadEverything"]
    }
  },
  "severity": {
    "minReportLevel": "medium",
    "failOn": "high"
  },
  "output": {
    "format": "text",
    "file": null
  },
  "dbPatterns": [
    {
      "orm": "custom",
      "methods": ["customQuery", "fetchRecords"]
    }
  ]
}

A config may also extends another config file, which is merged beneath it. See backend/.codeevolutionrc.example.json for a working example.

Severity Guidelines

Severity Criteria
Critical Security vulnerability, crashes, data loss
High Significant performance impact, memory leaks
Medium Noticeable degradation, code smell
Low Minor optimization, style suggestion

Next Steps