The no-cycle rule on 5,000 files: eslint-plugin-import takes 148 seconds;
eslint-plugin-import-next takes 2.71 — 54.9x measured, because O(n²)
graph traversal became O(n). At 10K files the gap projects past 100x (we stopped
measuring eslint-plugin-import at 5K — it was already taking ~2.5 minutes per
run). Full numbers, methodology, and a drop-in migration below.
🔄 Drop-in replacement — compatible with the
eslint-plugin-importrule set, with faster graph algorithms, CWE/LLM-optimized messages, and fewer false positives/negatives.
| Benchmark | 1K Files | 5K Files | 10K Files |
|---|---|---|---|
| Core Rules (9) | 1.6x | 3.3x | 5.2x |
| Recommended Preset | 1.4x | 3.0x | 5.5x |
| no-cycle Only | 25.7x | 54.9x | ~120x (projected) |
The 54.9x is measured; the 10K column is a projection (we stopped running
eslint-plugin-import at 5K — it was already ~2.5 minutes). Details below.
The original eslint-plugin-import uses an O(n²) module resolution algorithm:
- For each file, parse all imports
- For each import, resolve the full module path
- For
no-cycle, traverse the entire dependency graph for every file
This creates quadratic complexity. On 5,000 files with interconnected imports, the no-cycle rule alone takes 148 seconds.
We rewrote the core algorithms:
- Cached module resolution — resolve each path once, cache permanently
- Incremental graph building — build the dependency graph incrementally, not per-file
- Cycle detection with Tarjan's algorithm — O(n) instead of O(n²)
Result: 2.71 seconds for the same 5,000 files.
Both plugins configured with identical rules:
no-unresolved,named,namespace,default,exportno-named-as-default,no-named-as-default-member,no-duplicates,order
| Files | eslint-plugin-import | eslint-plugin-import-next | Speedup |
|---|---|---|---|
| 1,000 | 2.80s | 1.78s | 1.6x |
| 5,000 | 19.04s | 5.76s | 3.3x |
| 10,000 | 58.67s | 11.26s | 5.2x |
Takeaway: Even with basic rules, the performance gap grows with codebase size.
Using the full recommended configuration from each plugin.
| Files | eslint-plugin-import | eslint-plugin-import-next | Speedup |
|---|---|---|---|
| 1,000 | 2.42s | 1.78s | 1.4x |
| 5,000 | 18.43s | 6.07s | 3.0x |
| 10,000 | 57.74s | 10.57s | 5.5x |
Takeaway: Recommended presets show similar scaling — 5.5x faster at 10K files.
This is where the difference is massive. The no-cycle rule detects circular dependencies.
| Files | eslint-plugin-import | eslint-plugin-import-next | Speedup |
|---|---|---|---|
| 1,000 | 27.03s | 1.05s | 25.7x |
| 5,000 | 148.59s | 2.71s | 54.9x |
| 10,000 | ~600s (projected)* | ~5s (projected) | ~120x (projected) |
*10K Projection Note: 5K→10K doubles the file count, so O(n²) roughly quadruples eslint-plugin-import's time (148.59s × 4 ≈ 600s ≈ 10 minutes) — we didn't run it because 10+ minutes per iteration is impractical. eslint-plugin-import-next is O(n), so its time roughly doubles (2.71s × 2 ≈ 5s). 600 / 5 ≈ 120x — a projection, not a measurement; the measured maximum is the 54.9x at 5K.
Takeaway: If you use no-cycle (and you should), the speedup is 25-100x depending on codebase size.
┌────────────────────────────────────────────────────────────────┐
│ no-cycle Rule: 5,000 files │
├────────────────────────────────────────────────────────────────┤
│ eslint-plugin-import: 148.59s ████████████████████████████│
│ eslint-plugin-import-next: 2.71s █ │
└────────────────────────────────────────────────────────────────┘
Circular dependencies cause:
- Build failures with tree-shaking
- Runtime bugs with undefined imports
- Memory leaks in bundlers
- Test flakiness from initialization order
Most teams disable no-cycle because it's too slow. With eslint-plugin-import-next, you can finally enable it.
Apple-to-apple comparison — full source code
| Spec | Details |
|---|---|
| Codebase sizes | 1,000 / 5,000 / 10,000 JavaScript files |
| Iterations | 3-5 runs per size, per plugin |
| Fixtures | Realistic JS files with named/default imports, barrel files, cross-file dependencies |
| Environment | Node v20.19.5, Apple Silicon M1 (arm64), ESLint v9.17.0 |
| Cache | Cleared between each run |
git clone https://github.com/ofri-peretz/eslint-benchmark-suite.git
cd eslint-benchmark-suite
npm install
npm run generate:import
npm run benchmark:import
npm run benchmark:import-recommended
npm run benchmark:import-no-cycle
# Remove old plugin
npm uninstall eslint-plugin-import
# Install new plugin
npm install --save-dev eslint-plugin-import-next
// eslint.config.mjs — `configs` is a NAMED export (default export is the plugin)
import { configs } from "eslint-plugin-import-next";
export default [configs.recommended];
| Surface | Support |
|---|---|
| Package managers | npm, yarn, pnpm, bun |
| Node | >= 18.0.0 |
| ESLint | ^8.0.0 || ^9.0.0 || ^10.0.0, flat config |
| Compatibility | drop-in for the eslint-plugin-import rule set |
| Module system | Plugin ships CommonJS; your config can be eslint.config.js or .mjs |
| Oxlint | no-cycle flagship rule wired via the interlace-import-next port, parity-gated |
⭐ Star on GitHub if you've ever disabled no-cycle because it was too slow to run.
I'm Ofri Peretz, a security engineering leader and the author of the Interlace ESLint ecosystem — domain-specific static analysis for security, reliability, and performance on the Node.js stack.