Dead code accumulates in every frontend project over time. Features get deprecated, refactoring leaves remnants behind, and experiments never make it to production. This invisible weight slows builds, bloats bundles, and makes your codebase harder to maintain and understand.
The good news: detecting dead code doesn't require guesswork. Modern tooling provides multiple approaches to identify, measure, and eliminate code that serves no purpose. From compiler-level warnings to specialized linters to comprehensive project analyzers, you have everything needed to keep your codebase lean and performant.
This guide covers practical techniques for detecting dead code in frontend projects, with a focus on JavaScript and TypeScript applications. Combined with our /services/web-development/ expertise, these practices help maintain clean, efficient codebases.
Why Dead Code Matters
Dead code affects your project in ways that go beyond mere aesthetics. Every unused function, unreferenced variable, and unimported export adds to the cognitive load developers must navigate when understanding a codebase. In larger projects, finding the relevant code becomes like searching for a needle in a haystack.
Performance Impact
Performance implications compound as dead code accumulates. JavaScript bundles ship unused code to browsers, increasing download times and parsing overhead. For mobile users on slower connections, this translates directly to worse user experiences and higher bounce rates. Modern build tools attempt to eliminate dead code through tree-shaking, but they can only remove what they can prove is unused--and proving unused code is harder than it sounds.
Security Concerns
Security concerns also arise from dead code. Old authentication helpers, deprecated encryption utilities, or forgotten debugging endpoints might remain in your codebase long after their usefulness expires. These remnants could potentially be exploited if they're accidentally re-enabled or if their existence is forgotten during security reviews. Keeping your codebase clean reduces your attack surface and makes security audits more straightforward.
ESLint-Based Detection
Configuring ESLint for Dead Code Detection
ESLint provides foundational capabilities for detecting unused code through its core rules. The no-unused-vars rule catches variables, function parameters, and function declarations that aren't referenced anywhere in your codebase. For TypeScript projects, the @typescript-eslint/no-unused-vars rule offers additional configuration options tailored to TypeScript's type system.
// .eslintrc.js
module.exports = {
rules: {
'no-unused-vars': 'error',
'no-unused-expressions': 'error',
'no-unused-labels': 'error',
},
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
},
};
The no-unused-vars rule accepts several options that control its behavior. Setting argsIgnorePattern: '^_' allows you to prefix unused parameters with an underscore to indicate they're intentionally unused, such as callback parameters you need for API compatibility but don't use.
Detecting Unused Imports and Exports
Unused imports represent one of the most common forms of dead code in frontend projects. ESLint's no-unused-imports rule, available as a standalone plugin or through TypeScript ESLint, automatically removes import statements that don't reference any symbols from the imported module.
// .eslintrc.js with TypeScript ESLint
module.exports = {
plugins: ['@typescript-eslint'],
rules: {
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
'unused-imports/no-unused-imports': 'error',
},
};
For teams using modern JavaScript frameworks, pairing ESLint with Incremental Static Regeneration in Next.js helps maintain clean codebases as projects scale.
TypeScript Compiler Options
Using Compiler Flags for Dead Code Detection
TypeScript's compiler options provide powerful built-in mechanisms for detecting unused code at compile time. The --noUnusedLocals flag causes the compiler to report errors when local variables are declared but never used. Similarly, --noUnusedParameters flags function parameters that aren't referenced within the function body.
{
"compilerOptions": {
"noUnusedLocals": true,
"noUnusedParameters": true,
"strict": true
}
}
These flags operate at the TypeScript level, meaning they catch not just unused values but also unused types. An exported interface that no longer appears in any public API will trigger a warning, as will type aliases that have become obsolete through refactoring.
Handling Intentionally Unused Code
Sometimes code is intentionally unused. Prefix a variable or parameter with an underscore to signal that its unused status is intentional. TypeScript's compiler recognizes this pattern and won't flag the symbol as unused.
// Intentionally unused parameter - compiler won't warn
function handleEvent(eventName: string, _handler: () => void) {
console.log(`Event registered: ${eventName}`);
// _handler is reserved for future use
}
// Unused export that's part of public API
export interface LegacyConfig {
/** @deprecated Use NewConfig instead */
oldField: string;
}
If you're building GraphQL-powered applications, these TypeScript practices complement our guide on building GraphQL React apps with TypeScript for comprehensive code quality.
Specialized Dead Code Detection Tools
ts-prune: Finding Unused Exports
The ts-prune tool analyzes your entire project to find exported symbols that aren't imported anywhere. Unlike compiler options, it understands your project's dependency graph and can identify exports that are truly dead--not just locally unused.
# Install ts-prune
npm install --save-dev ts-prune
# Run against your project
npx ts-prune -p tsconfig.json
Creating a dedicated tsconfig file for ts-prune allows you to specify exactly which entry points to analyze, ignoring test files and examples that shouldn't be considered when determining what's "alive" in your application.
Knip: Comprehensive Project Analysis
Knip represents the modern evolution of dead code detection, offering a comprehensive solution that finds unused dependencies, exports, and files across your entire project. With over 100 plugins for popular frameworks and tools, Knip understands how each tool integrates with your project.
# Install Knip
npm install --save-dev knip
# Run Knip analysis
npx knip
Major technology companies including Adobe, AWS, Anthropic, Astro, and Backstage use Knip to maintain clean codebases. This track record demonstrates that the tool scales from small projects to enterprise applications with complex dependency graphs. Combined with understanding why TypeScript enums have limitations, these tools help maintain pristine codebases.
Bundle Analysis for Production Builds
Using Bundle Analyzers
While source-level dead code detection catches unused code before it reaches production, bundle analyzers reveal what's actually being shipped. Tools like webpack-bundle-analyzer show you exactly which code made it into your production bundles.
// webpack.config.js with bundle analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: false,
}),
],
};
Bundle analysis is particularly valuable for identifying unused code from third-party dependencies. Even if you've carefully maintained your application code, dependencies can accumulate over time.
Tree-Shaking and Dead Code Elimination
Modern bundlers perform tree-shaking to eliminate unused code during the build process. This optimization works by analyzing the module dependency graph and excluding any code that can't be reached from entry points.
{
"sideEffects": false,
"moduleType": "esnext"
}
The sideEffects field in package.json signals whether importing a module can have effects beyond returning its exports. Setting this to false tells bundlers that the package is safe to eliminate entirely if its exports aren't used. These optimization techniques complement top JavaScript data visualization libraries when building performance-conscious applications.
Integration into Development Workflow
Pre-commit Hooks and CI/CD
Dead code detection becomes most effective when integrated into your regular workflow. Pre-commit hooks using husky run linters and static analysis before code reaches your repository, catching issues early.
// .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
Continuous integration pipelines should include dead code checks as part of the quality gate. Running ts-prune, Knip, or ESLint with unused variable rules on every pull request prevents dead code from reaching your main branch.
Regular Cleanup Sprints
Beyond automated checks, scheduling regular cleanup sprints ensures that accumulated debt gets addressed. Set a calendar reminder to run comprehensive dead code detection monthly or quarterly. Prioritize high-impact removals: large unused dependencies, frequently-visited files with many unused functions, and deprecated APIs. Similar attention to memory leaks in Node.js applications helps maintain overall system health.
Best Practices for Prevention
Code Review Checklist
Make dead code awareness part of your code review process. Reviewers should ask whether removed code has been deleted rather than commented out, whether refactored features cleaned up their associated utilities, and whether deprecated flags have been fully removed.
Adding explicit questions to your review checklist standardizes this attention:
- Are all new dependencies necessary?
- Has dead code been removed?
- Are deprecated functions marked and scheduled for removal?
Documentation and Communication
When removing code, especially code that might be referenced by external documentation or other teams, communicate changes clearly. Deprecation warnings in code help consumers of your API understand that functionality is going away. Blog posts or changelog entries explain what's changing and why.
For internal code, maintain a changelog or architecture decision record that notes significant dead code removal. Future developers investigating why certain approaches weren't used will find context in these records.
Conclusion
Detecting dead code in frontend projects requires a multi-layered approach:
| Approach | Tool | What It Catches |
|---|---|---|
| ESLint | no-unused-vars, TypeScript ESLint | Unused variables, functions, imports |
| TypeScript Compiler | --noUnusedLocals, --noUnusedParameters | Unused types, parameters, local declarations |
| Specialized Tools | ts-prune, Knip | Unused exports, unused dependencies |
| Bundle Analysis | webpack-bundle-analyzer | Dead code in production bundles |
The most effective strategy combines multiple approaches tailored to your project's technology stack and workflow. Start with basic ESLint and TypeScript configuration--low-hanging fruit that requires minimal setup. Add specialized tools as your project grows.
The investment in keeping your codebase clean pays dividends in maintainability, performance, and developer productivity. Every unused function removed reduces cognitive load. Every deleted dependency shrinks your attack surface. Dead code isn't just aesthetic polish--it's a marker of engineering discipline that compounds over time.