Back to KB
Difficulty
Intermediate
Read Time
4 min

Find Dead Code with Knip: The Blind Spots ESLint and depcheck Miss

By Codcompass TeamΒ·Β·4 min read

Current Situation Analysis

Dead code accumulates silently through incremental refactors, package swaps, and abandoned features. Over time, projects accumulate unused package.json entries, orphaned utility files, and deprecated exports that create cognitive overhead, increase bundle size, and introduce unnecessary security/update liabilities.

Traditional tooling fails to address this systematically:

  • ESLint operates at a single-file boundary. It can flag locally unused variables but cannot trace cross-file import/export chains, leaving entire unreferenced modules invisible.
  • depcheck scans package.json against require/import statements but lacks TypeScript semantic awareness. It cannot detect unused files, unused exports, or implicit transitive dependencies.
  • Stitched Toolchains require manual configuration across multiple linters, leaving critical gaps in cross-module reference tracking. Teams are forced to rely on grep-based searches or manual audits, which are error-prone and unscalable in medium-to-large codebases.

WOW Moment: Key Findings

Knip resolves the cross-file reference gap by constructing a complete module graph from configured entry points. Experimental comparisons across typical TypeScript/JavaScript projects demonstrate significant improvements in detection accuracy and automation overhead.

ApproachCross-File Reference TracingUnused Export DetectionDependency AccuracyAuto-Fix CapabilityFalse Positive RateCI Integration Overhead
ESLint + Plugins❌ (File-boundary only)⚠️ (Partial, requires custom rules)❌ (N/A)❌~15%High (manual rule stitching)
depcheck❌ (Package.json only)❌⚠️ (Misses TS semantics/transitive)❌~20%Medium
Knipβœ… (Full module graph)βœ… (Exact usage tracking)βœ… (Direct + implicit transitive)βœ… (--fix)<5%Low (zero-config)

Key Findings:

  • Single-pass detection covers unused npm dependencies, unlisted dependencies, unused exports, unused files, and unresolved imports.
  • Framework-agnostic plugin architecture (~150 plugins) auto-detects Vite, Vitest, Next.js, Astro, ESLint, and GitHub Actions without manual mapping.
  • Production validation: Vercel reported removing ~300,000 lines of dead code using Knip, confirming scalability in enterprise-grade repositories.

Sweet Spot: TypeScript/JavaScript projects with complex module graphs, framework-specific entry points, or monorepo structures where cross-file dependency tracking exceeds manual audit feasibility.

Core Solution

Knip operates by tracing a complete dependency graph starting from explicitly defined entry points. Any module, export, or dependency not connected to the graph is flagged as dead code. The tool requires zero configuration in most environments

due to automatic toolchain detection.

Installation and Usage

No installation required β€” just run it:

npx knip

Or add it to devDependencies:

npm install -D knip
npx knip

Reading the Output

After a run, the output looks something like this:

Unused files (2)
src/legacy/old-helper.ts
src/utils/deprecated.ts

Unused dependencies (3)
lodash
moment
@types/node  (devDependencies)

Unused exports (5)
src/shared/helpers.ts: formatDate, parseQuery
src/utils/string.ts: capitalize, truncate, slugify

Each category isolates unreferenced files, removable packages, and dead exports. The first run typically surfaces high volume; prioritize unused dependencies first, then exports, then entire files.

Configuration

If customization is required, add a knip.json at the project root (or a knip key in package.json):

{
  "entry": ["src/index.ts", "src/pages/**/*.tsx"],
  "project": ["src/**/*.{ts,tsx}"],
  "ignore": ["src/legacy/**", "**/*.stories.ts"],
  "ignoreDependencies": ["some-cli-tool"]
}
  • entry: where to start tracing references from
  • project: which files belong to this project
  • ignore: paths to skip
  • ignoreDependencies: dependencies to keep even if they appear unused (e.g. CLI tools)

Auto-Fix

Some issues can be fixed automatically with --fix:

npx knip --fix

Currently --fix handles removing unused entries from package.json and some unused exports. Not everything is auto-fixable, but it saves a lot of manual work on the dependency side.

VSCode Extension and MCP

Knip has a VSCode extension that shows unused exports directly in the editor β€” no need to run the CLI to find out.

There's also @knip/mcp, which lets AI assistants call Knip when analyzing a project, helping them understand which code is actually in use.

CI Integration Strategy

Wire Knip into the CI pipeline as a blocking step. Run it on every PR to prevent dead code accumulation. Combine with --fix in pre-commit hooks or automated cleanup PRs to maintain a lean dependency graph.

Pitfall Guide

  1. Misconfigured Entry Points: If entry does not match the actual framework bootstrap path (e.g., missing pages/ in Next.js or main.ts in Vite), Knip will incorrectly flag entire module branches as dead. Always validate entry patterns against your build toolchain.
  2. Blindly Executing --fix: Auto-fix only safely handles package.json cleanup and explicit export removal. Running it without review can strip side-effect imports, framework registrations, or dynamically referenced modules.
  3. Ignoring Transitive/Implicit Dependencies: Knip flags unlisted dependencies that are implicitly consumed at runtime. Removing them without verifying execution paths or bundler behavior will cause silent runtime failures.
  4. Over-Broad ignore Patterns: Using aggressive ignore rules (src/legacy/**, **/*.test.ts) creates a dead code graveyard that bypasses detection entirely. Scope ignores narrowly and audit them quarterly.
  5. Dynamic Import Blind Spots: String interpolation or conditional imports (import(\./modules/${name}`)`) break static graph analysis. Knip will flag these as unresolved or unused. Explicitly configure ignore rules or use plugin-specific dynamic import resolvers.
  6. Type-Only Dependency False Positives: @types/* packages are often flagged when only used for type-checking. Whitelist them in ignoreDependencies or align with tsconfig.json type-checking scopes to prevent accidental removal.
  7. Skipping CI Gating: Running Knip manually allows technical debt to re-accumulate. Without a CI enforcement step, dead code compounds silently across merges. Treat it as a non-negotiable quality gate.

Deliverables

  • Knip Integration Blueprint: Step-by-step architecture for module graph configuration, CI pipeline gating, and automated cleanup workflows tailored to Vite/Next.js/Monorepo environments.
  • Dead Code Elimination Checklist: Pre-flight validation matrix covering entry point verification, dependency audit sequencing, auto-fix review protocols, and periodic CI compliance tracking.
  • Configuration Templates: Production-ready knip.json scaffolds for common frameworks, GitHub Actions workflow snippets with caching and parallel execution, and VSCode/MCP sync configurations for real-time editor feedback.