# No circular module dependencies

- **Pattern:** `ab-002150` (`code-quality-essentials.dependencies.no-circular`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002150
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002150)

## Why it matters

Circular module dependencies cause subtle initialization-order bugs that appear only in specific import sequences — the kind that works on the developer's machine and fails in a bundled production build or a Jest environment with a different module evaluation order. ISO 25010:2011 §6.5.1 classifies this as a structural maintainability defect. Circular dependencies also prevent effective tree-shaking: bundlers cannot safely eliminate one half of a cycle, so both modules ship to the client even when only one is needed.

## Severity rationale

High because circular imports introduce initialization-order bugs that are environment-specific and notoriously hard to diagnose, and they prevent the bundler from tree-shaking either module.

## Remediation

Detect cycles with `dpdm` or `madge`, then break them by extracting the shared dependency into a third module:

```bash
npx dpdm --circular --tree false src/**/*.ts
# or
npx madge --circular src/
```

Breaking the cycle:
```
Before:
  utils/format.ts → features/user.ts → utils/format.ts  (circular)

After:
  types/user.types.ts  (new shared module)
  utils/format.ts → types/user.types.ts
  features/user.ts → types/user.types.ts
```

Add to CI to prevent regressions:
```bash
npx madge --circular --extensions ts src/ && echo "No circular deps"
```

## Detection

- **ID:** `no-circular`
- **Severity:** `high`
- **What to look for:** Circular dependencies occur when module A imports from module B, and module B (directly or indirectly) imports from module A. Look for this pattern in `src/` by examining import statements across sibling directories. Warning signs: utility files that import from feature modules, feature modules that import from each other bidirectionally, index files that re-export from multiple directories that also import from each other. Check `package.json` for `dpdm` or `madge` in devDependencies — these tools detect circular dependencies automatically. Also check if the bundler (webpack, Vite) has reported circular dependency warnings. Circular dependencies cause subtle initialization order bugs and make tree-shaking less effective.
- **Pass criteria:** Enumerate all import chains. No circular dependencies detected, OR `dpdm`/`madge` runs in CI and reports no cycles, OR any tolerated cycles are documented with justification.
- **Fail criteria:** Visible circular import patterns, OR circular dependency tool reports cycles without documented justification.
- **Skip (N/A) when:** Project is too small to have import cycles (fewer than 10 modules).
- **Detail on fail:** `"Potential circular dependency detected: e.g., src/utils/A.ts imports from src/features/B.ts which imports from src/utils/A.ts"`
- **Remediation:** To detect cycles, run:

  ```bash
  npx dpdm --circular --tree false src/**/*.ts
  # or
  npx madge --circular src/
  ```

  To break a cycle, extract the shared dependency into a third module:

  ```
  Before:
    utils/format.ts → features/user.ts → utils/format.ts  (circular)

  After:
    types/user.types.ts  (shared)
    utils/format.ts → types/user.types.ts
    features/user.ts → types/user.types.ts
  ```

  Add `madge` to CI to prevent regressions:
  ```bash
  npx madge --circular --extensions ts src/ && echo "No circular deps"
  ```

## External references

- iso-25010 maintainability.modularity

Taxons: code-quality

HTML version: https://auditbuffet.com/patterns/ab-002150
