Dead Code Eliminator
Systematic detection and safe removal of dead code from codebases. This skill covers static analysis techniques, call graph construction, feature flag cleanup, and safe removal strategies that minimize the risk of accidentally removing code that is still needed through indirect references, reflection, or external integrations.
When to Use This Skill
Use this skill for:
- Cleaning up a codebase before a major release or refactoring effort
- Removing code left behind after a feature deprecation or migration
- Eliminating unused imports, variables, functions, classes, and modules
- Cleaning up stale feature flags and their associated code paths
- Reducing bundle size, compilation time, or test execution time
- Improving code readability by removing distracting dead code
- Preparing a codebase for transfer to a new team or open-sourcing
Trigger phrases: "dead code", "unused code", "remove dead code", "unused imports", "unreachable code", "unused functions", "feature flag cleanup", "stale code", "code cleanup", "eliminate dead code", "unused variables", "orphan code"
What This Skill Does
This skill provides a structured approach to dead code elimination:
- Static Analysis: Identifies unused imports, variables, functions, classes, and modules using language-specific analysis techniques
- Call Graph Analysis: Constructs function-level and module-level call graphs to identify unreachable code from known entry points
- Feature Flag Cleanup: Identifies stale feature flags and guides safe removal of both the flag checks and the dead code branches
- Dynamic Analysis Guidance: Recommends runtime instrumentation approaches for code where static analysis alone is insufficient
- Safe Removal Strategies: Provides step-by-step procedures for removing dead code while minimizing the risk of breaking hidden dependencies
- Verification Procedures: Defines testing and validation steps to confirm that removal is safe
Instructions
Step 1: Categorize Dead Code Types
Understand the different categories of dead code, each requiring a different detection approach.
| Category |
Description |
Detection Difficulty |
Risk of False Positive |
| Unused Imports |
Imported modules, packages, or symbols never referenced |
Easy |
Low |
| Unused Variables |
Declared variables never read |
Easy |
Low |
| Unused Functions/Methods |
Defined but never called within the codebase |
Medium |
Medium (reflection, callbacks) |
| Unused Classes |
Defined but never instantiated or referenced |
Medium |
Medium (dependency injection, serialization) |
| Unreachable Code |
Code after unconditional return/throw/break, impossible conditions |
Easy |
Low |
| Dead Conditional Branches |
Branches that can never execute due to constant conditions |
Medium |
Low |
| Obsolete Feature Code |
Entire features that have been superseded or disabled |
Hard |
High (might be re-enabled) |
| Stale Feature Flags |
Feature flags that have been permanently enabled or disabled |
Medium |
Medium (rollback scenarios) |
| Unused Configuration |
Config entries, environment variables, or constants never read |
Hard |
High (external consumers) |
| Orphaned Test Code |
Tests for functions or classes that no longer exist |
Medium |
Low |
Step 2: Detect Dead Code Using Static Analysis
Apply language-specific static analysis to identify dead code candidates.
Python Example: Detecting Unused Code
# DEAD CODE ANALYSIS RESULTS:
# 1. Unused import: 'json' (imported but never used)
# 2. Unused variable: 'temp_result' (assigned but never read)
# 3. Unused function: 'legacy_format_output' (defined but never called)
# 4. Unreachable code: lines after 'return' in 'process_data'
# 5. Dead conditional: 'if False:' block
import os
import json # DEAD: unused import
import logging
from typing import List, Optional
from dataclasses import dataclass
logger = logging.getLogger(__name__)
LEGACY_MODE = False # Constant, never changed at runtime
@dataclass
class DataRecord:
id: str
value: float
category: str
def process_data(records: List[DataRecord]) -> dict:
"""Process records and return summary."""
if not records:
return {"count": 0, "total": 0.0}
total = sum(r.value for r in records)
temp_result = total * 1.1 # DEAD: unused variable, never read
result = {
"count": len(records),
"total": total,
"average": total / len(records),
}
return result
# DEAD: unreachable code after return
logger.info("Processing complete")
notify_downstream(result)
def legacy_format_output(data: dict) -> str:
# DEAD: this function is never called anywhere in the codebase
"""Format output in legacy XML format."""
parts = []
for key, value in data.items():
parts.append(f"<{key}>{value}</{key}>")
return "<result>" + "".join(parts) + "</result>"
def format_output(data: dict) -> str:
"""Format output as JSON string."""
return str(data)
def main():
if LEGACY_MODE:
# DEAD: conditional branch that never executes (LEGACY_MODE = False)
logger.info("Running in legacy mode")
records = load_legacy_records()
else:
records = load_records()
result = process_data(records)
output = format_output(result)
print(output)
Static analysis tools by language:
| Language |
Tool |
Command |
What It Detects |
| Python |
vulture |
vulture src/ |
Unused functions, variables, imports, classes |
| Python |
autoflake |
autoflake --check src/ |
Unused imports and variables |
| Python |
pylint |
pylint --disable=all --enable=W0611,W0612 src/ |
Unused imports (W0611), unused variables (W0612) |
| JavaScript |
ESLint no-unused-vars |
eslint --rule 'no-unused-vars: error' src/ |
Unused variables, imports, functions |
| JavaScript |
ts-prune |
ts-prune |
Unused exports in TypeScript |
| Java |
IntelliJ / Eclipse |
Built-in inspection |
Unused declarations, unreachable code |
| Java |
SpotBugs |
mvn spotbugs:check |
Dead local stores, unused fields |
| Java |
PMD |
pmd check --rulesets category/java/bestpractices.xml |
Unused imports, variables, private methods |
JavaScript Example: Detecting Unused Exports and Functions
// file: src/utils/formatting.js
// DEAD: exported but never imported anywhere
export function formatLegacyDate(date) {
const d = new Date(date);
return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`;
}
// ACTIVE: imported by 3 modules
export function formatISODate(date) {
return new Date(date).toISOString().split("T")[0];
}
// DEAD: exported but never imported anywhere
export function formatCurrency(amount, currency = "USD") {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency,
}).format(amount);
}
// DEAD: internal helper, only called by formatLegacyDate (which is also dead)
function padZero(num) {
return num < 10 ? `0${num}` : String(num);
}
// ACTIVE: called by formatISODate
function validateDate(date) {
const d = new Date(date);
if (isNaN(d.getTime())) {
throw new Error(`Invalid date: ${date}`);
}
return d;
}
Java Example: Detecting Unused Code with Call Graph
// DEAD CODE ANALYSIS:
// 1. LegacyReportGenerator -- class never instantiated or referenced
// 2. UserService.getInactiveUsers() -- method never called
// 3. REPORT_VERSION constant -- never read
// 4. Unused import: java.util.LinkedList
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList; // DEAD: unused import
import java.util.Map;
import java.util.stream.Collectors;
public class UserService {
private static final String REPORT_VERSION = "2.1"; // DEAD: never read
private final UserRepository userRepository;
private final EmailService emailService;
// ACTIVE: called from UserController.getUsers()
public List<UserDTO> getActiveUsers() {
return userRepository.findByStatus(Status.ACTIVE)
.stream()
.map(this::toDTO)
.collect(Collectors.toList());
}
// DEAD: never called from any reachable code path
public List<UserDTO> getInactiveUsers() {
return userRepository.findByStatus(Status.INACTIVE)
.stream()
.map(this::toDTO)
.collect(Collectors.toList());
}
// ACTIVE: called by getActiveUsers (and would be called by getInactiveUsers)
private UserDTO toDTO(User user) {
return new UserDTO(user.getId(), user.getName(), user.getEmail());
}
}
// DEAD: entire class is never referenced anywhere in the codebase
public class LegacyReportGenerator {
public String generateReport(List<UserDTO> users) {
StringBuilder sb = new StringBuilder();
sb.append("REPORT\n");
sb.append("======\n");
for (UserDTO user : users) {
sb.append(user.getName()).append("\n");
}
return sb.toString();
}
}
Step 3: Build and Analyze Call Graphs
For non-trivial dead code detection, construct a call graph starting from known entry points.
Call Graph Construction Process
- Identify entry points: main methods, HTTP endpoints, event handlers, scheduled tasks, CLI commands, test methods
- Build the forward call graph: for each entry point, trace all functions/methods that are reachable through direct calls
- Identify unreachable nodes: any function not reachable from any entry point is a candidate for removal
- Check for indirect references: search for reflection, dynamic dispatch, dependency injection, serialization, and string-based method references that static analysis misses
Python Example: Simple Call Graph Builder
import ast
import os
from collections import defaultdict
from typing import Dict, Set
class CallGraphBuilder(ast.NodeVisitor):
"""Build a simple call graph from Python source files."""
def __init__(self):
self.definitions: Dict[str, str] = {} # func_name -> file
self.calls: Dict[str, Set[str]] = defaultdict(set) # caller -> callees
self.current_function: str | None = None
def visit_FunctionDef(self, node):
old_function = self.current_function
self.current_function = node.name
self.definitions[node.name] = self._current_file
self.generic_visit(node)
self.current_function = old_function
def visit_Call(self, node):
if self.current_function and isinstance(node.func, ast.Name):
self.calls[self.current_function].add(node.func.id)
self.generic_visit(node)
def analyze_file(self, filepath: str):
self._current_file = filepath
with open(filepath) as f:
tree = ast.parse(f.read())
self.visit(tree)
def find_unreachable(self, entry_points: Set[str]) -> Set[str]:
"""Find functions not reachable from any entry point."""
reachable = set()
stack = list(entry_points)
while stack:
func = stack.pop()
if func in reachable:
continue
reachable.add(func)
for callee in self.calls.get(func, set()):
if callee not in reachable:
stack.append(callee)
all_defined = set(self.definitions.keys())
return all_defined - reachable
# Usage
builder = CallGraphBuilder()
for root, dirs, files in os.walk("src"):
for f in files:
if f.endswith(".py"):
builder.analyze_file(os.path.join(root, f))
entry_points = {"main", "handle_request", "process_event"}
unreachable = builder.find_unreachable(entry_points)
print(f"Potentially dead functions: {unreachable}")
Step 4: Handle Special Cases
Static analysis and call graphs miss certain categories of "hidden" usage. Check each dead code candidate against these patterns before removal.
Hidden Usage Patterns
| Pattern |
How It Hides Usage |
Detection Strategy |
| Reflection |
getattr(obj, method_name), Class.forName() |
Search for reflection APIs; grep for function names as strings |
| Dynamic Dispatch |
Plugin systems, strategy patterns via config |
Check configuration files, plugin registries |
| Dependency Injection |
Framework creates instances via config |
Check DI container configs (Spring XML, Guice modules) |
| Serialization |
Fields used only during JSON/XML serialization |
Check @JsonProperty, @XmlElement, Serializable annotations |
| External API |
Public library methods called by external consumers |
Check if the code is a library with external dependents |
| Database Mapping |
ORM fields mapped to DB columns but not accessed in code |
Check ORM mappings (Hibernate, SQLAlchemy, Prisma) |
| Template Engines |
Functions called from HTML/template files |
Search template files for function references |
| Scheduled Tasks |
Methods invoked by cron or task scheduler |
Check scheduler configs, @Scheduled annotations |
| Message Handlers |
Methods triggered by message queue consumers |
Check message broker configs, @EventListener annotations |
Verification Grep Patterns
# Search for function name used as a string (reflection risk)
# Replace "myFunction" with the candidate dead function name
grep -r '"myFunction"' --include="*.py" --include="*.js" --include="*.java" src/
grep -r "'myFunction'" --include="*.py" src/
# Search in configuration files
grep -r "myFunction" --include="*.xml" --include="*.yaml" --include="*.json" .
# Search in template files
grep -r "myFunction" --include="*.html" --include="*.jinja2" --include="*.ejs" .
# Search in test files (dead code might be tested but unused in production)
grep -r "myFunction" --include="*.test.*" --include="*_test.*" --include="*Test.java" .
Step 5: Clean Up Feature Flags
Feature flags that have been permanently enabled or disabled leave behind dead code paths that should be cleaned up.
Feature Flag Cleanup Process
- Inventory all feature flags: list every flag, its current state, and when it was last changed
- Identify stale flags: flags that have been in the same state (enabled or disabled) for longer than the team's flag lifecycle policy (typically 30-90 days after full rollout)
- Determine the live branch: for each stale flag, identify which code path is active and which is dead
- Remove the dead branch: delete the code in the inactive branch
- Remove the flag check: replace the conditional with just the live branch code
- Remove the flag definition: delete the flag from configuration, launch darkly, or wherever it is defined
JavaScript Example: Feature Flag Cleanup
// BEFORE: Stale feature flag "new_checkout_flow" has been enabled for 6 months
import { isEnabled } from "./featureFlags";
async function processCheckout(cart) {
if (isEnabled("new_checkout_flow")) {
// This is the LIVE path (flag has been enabled for 6 months)
const order = await createOrderV2(cart);
await processPaymentV2(order);
await sendConfirmationV2(order);
return order;
} else {
// This is the DEAD path (flag is always enabled, this never executes)
const order = await createOrder(cart);
await processPayment(order);
await sendConfirmation(order);
return order;
}
}
// AFTER: Flag removed, dead branch deleted
async function processCheckout(cart) {
const order = await createOrderV2(cart);
await processPaymentV2(order);
await sendConfirmationV2(order);
return order;
}
// ALSO REMOVE:
// - createOrder, processPayment, sendConfirmation (if only called from dead path)
// - "new_checkout_flow" from feature flag configuration
// - Any tests that specifically tested the old checkout flow
Step 6: Safe Removal Strategy
Follow a systematic process to remove dead code safely.
Removal Procedure
- Mark, do not delete: first, add deprecation annotations or comments to candidate dead code; deploy and monitor for a release cycle
- Add logging (optional): for uncertain cases, add a log statement inside the suspected dead code and monitor logs for a period; if the log never fires, the code is confirmed dead
- Remove in small batches: delete dead code in focused commits (one logical group per commit) so that any regression can be easily traced and reverted
- Run the full test suite: after each removal, run all tests (unit, integration, end-to-end) and verify nothing breaks
- Deploy to staging: verify the removal in a staging environment before production
- Monitor after deployment: watch error rates, logs, and metrics for 24-48 hours after deploying dead code removal to production
Python Example: Gradual Removal with Logging
import logging
import warnings
logger = logging.getLogger(__name__)
# Step 1: Mark as deprecated (release N)
@deprecated("This function is believed to be dead code. "
"If you see this warning, contact the platform team.")
def legacy_format_output(data: dict) -> str:
# Step 2: Add monitoring
logger.warning(
"legacy_format_output was called -- this was believed to be dead code",
extra={"caller": inspect.stack()[1]},
)
# Original implementation
parts = []
for key, value in data.items():
parts.append(f"<{key}>{value}</{key}>")
return "<result>" + "".join(parts) + "</result>"
# Step 3: After monitoring period confirms no calls, remove entirely (release N+1)
# Delete the function and all references
Java Example: Safe Removal with @Deprecated
// Step 1: Mark as deprecated (release N)
/**
* @deprecated This method is believed to be dead code as of 2024-01.
* If you encounter this deprecation warning, contact the
* platform team. Scheduled for removal in release 2024-Q2.
*/
@Deprecated(since = "2024-01", forRemoval = true)
public List<UserDTO> getInactiveUsers() {
logger.warn("getInactiveUsers() was called -- believed to be dead code");
return userRepository.findByStatus(Status.INACTIVE)
.stream()
.map(this::toDTO)
.collect(Collectors.toList());
}
// Step 2: After monitoring confirms no calls, remove in next release
Step 7: Generate the Dead Code Report
## Dead Code Analysis Report
### Summary
- **Files analyzed**: {count}
- **Dead code candidates found**: {count}
- **Estimated removable lines**: {count}
- **Estimated size reduction**: {percentage or KB}
### Findings by Category
| Category | Count | Lines | Confidence |
|----------|-------|-------|------------|
| Unused imports | {n} | {lines} | High |
| Unused variables | {n} | {lines} | High |
| Unused functions/methods | {n} | {lines} | Medium |
| Unused classes | {n} | {lines} | Medium |
| Unreachable code | {n} | {lines} | High |
| Stale feature flags | {n} | {lines} | Medium |
| Obsolete feature code | {n} | {lines} | Low-Medium |
### Detailed Findings
#### 1. {Dead Code Item}
- **Location**: {file}:{line range}
- **Type**: {category}
- **Confidence**: {high/medium/low}
- **Reason**: {why this is believed to be dead}
- **Hidden usage check**: {reflection: no, DI: no, serialization: no, ...}
- **Recommended action**: {remove / deprecate first / investigate}
### Removal Plan
- **Phase 1 (safe, immediate)**: {high-confidence items}
- **Phase 2 (deprecate and monitor)**: {medium-confidence items}
- **Phase 3 (investigate)**: {low-confidence items requiring further analysis}
Best Practices
- Start with high-confidence, low-risk removals: unused imports and unreachable code after return statements are safe to remove immediately; build confidence before tackling uncertain cases
- Use version control as your safety net: always commit before removing dead code; if something breaks, you can revert the specific removal commit
- Remove dead code before adding new features: cleaning up dead code first reduces confusion and merge conflicts when new feature code is added
- Do not comment out code instead of deleting it: commented-out code is still dead code and adds visual noise; rely on version control history to recover deleted code if needed
- Clean up related artifacts: when removing a dead function, also remove its tests, documentation, configuration entries, and any supporting helper functions that become dead as a result
- Automate detection in CI/CD: configure linters and static analysis tools to flag unused imports and variables on every pull request to prevent new dead code from accumulating
- Set a regular cleanup cadence: schedule dead code analysis quarterly or after major feature launches to prevent gradual accumulation
- Document removal decisions: in the commit message, briefly explain why the code was determined to be dead and what analysis was performed
Common Pitfalls
- Removing code used via reflection or dynamic dispatch: static analysis cannot detect usage through
getattr(), Class.forName(), or plugin systems; always check for string-based references before removing
- Removing public library APIs: if the codebase is a library consumed by external projects, "unused" functions may have external callers that are invisible to your analysis; check download/usage metrics and maintain backward compatibility
- Removing code referenced in configuration files: functions referenced in Spring XML, Guice modules, routing tables, or scheduler configs appear unused in code but are invoked at runtime
- Removing ORM-mapped fields: database column mappings may appear unused in application code but are required for correct serialization and deserialization
- Deleting "unused" event handlers or webhooks: code that handles incoming webhooks, message queue events, or scheduled triggers may appear dead because the trigger is external
- Confusing test-only code with dead code: helper functions used exclusively in test files are not dead code; they are test utilities
- Removing code too aggressively in a single commit: large-scale removal makes it difficult to identify which specific deletion caused a regression; remove in small, focused batches
- Not monitoring after removal: even after thorough analysis, some dead code may have hidden callers that only manifest under specific conditions (monthly batch jobs, annual reports, error recovery paths); monitor for a full business cycle after removal
1---2name: dead-code-eliminator3description: Find and safely remove dead code including unreachable functions, unused imports, obsolete features, and stale feature flags using static analysis and call.4---56# Dead Code Eliminator78Systematic detection and safe removal of dead code from codebases. This skill covers static analysis techniques, call graph construction, feature flag cleanup, and safe removal strategies that minimize the risk of accidentally removing code that is still needed through indirect references, reflection, or external integrations.910## When to Use This Skill1112Use this skill for:1314- Cleaning up a codebase before a major release or refactoring effort15- Removing code left behind after a feature deprecation or migration16- Eliminating unused imports, variables, functions, classes, and modules17- Cleaning up stale feature flags and their associated code paths18- Reducing bundle size, compilation time, or test execution time19- Improving code readability by removing distracting dead code20- Preparing a codebase for transfer to a new team or open-sourcing2122**Trigger phrases**: "dead code", "unused code", "remove dead code", "unused imports", "unreachable code", "unused functions", "feature flag cleanup", "stale code", "code cleanup", "eliminate dead code", "unused variables", "orphan code"2324## What This Skill Does2526This skill provides a structured approach to dead code elimination:2728- **Static Analysis**: Identifies unused imports, variables, functions, classes, and modules using language-specific analysis techniques29- **Call Graph Analysis**: Constructs function-level and module-level call graphs to identify unreachable code from known entry points30- **Feature Flag Cleanup**: Identifies stale feature flags and guides safe removal of both the flag checks and the dead code branches31- **Dynamic Analysis Guidance**: Recommends runtime instrumentation approaches for code where static analysis alone is insufficient32- **Safe Removal Strategies**: Provides step-by-step procedures for removing dead code while minimizing the risk of breaking hidden dependencies33- **Verification Procedures**: Defines testing and validation steps to confirm that removal is safe3435## Instructions3637### Step 1: Categorize Dead Code Types3839Understand the different categories of dead code, each requiring a different detection approach.4041| Category | Description | Detection Difficulty | Risk of False Positive |42|----------|-------------|---------------------|----------------------|43| **Unused Imports** | Imported modules, packages, or symbols never referenced | Easy | Low |44| **Unused Variables** | Declared variables never read | Easy | Low |45| **Unused Functions/Methods** | Defined but never called within the codebase | Medium | Medium (reflection, callbacks) |46| **Unused Classes** | Defined but never instantiated or referenced | Medium | Medium (dependency injection, serialization) |47| **Unreachable Code** | Code after unconditional return/throw/break, impossible conditions | Easy | Low |48| **Dead Conditional Branches** | Branches that can never execute due to constant conditions | Medium | Low |49| **Obsolete Feature Code** | Entire features that have been superseded or disabled | Hard | High (might be re-enabled) |50| **Stale Feature Flags** | Feature flags that have been permanently enabled or disabled | Medium | Medium (rollback scenarios) |51| **Unused Configuration** | Config entries, environment variables, or constants never read | Hard | High (external consumers) |52| **Orphaned Test Code** | Tests for functions or classes that no longer exist | Medium | Low |5354### Step 2: Detect Dead Code Using Static Analysis5556Apply language-specific static analysis to identify dead code candidates.5758#### Python Example: Detecting Unused Code5960```python61# DEAD CODE ANALYSIS RESULTS:62# 1. Unused import: 'json' (imported but never used)63# 2. Unused variable: 'temp_result' (assigned but never read)64# 3. Unused function: 'legacy_format_output' (defined but never called)65# 4. Unreachable code: lines after 'return' in 'process_data'66# 5. Dead conditional: 'if False:' block6768import os69import json # DEAD: unused import70import logging71from typing import List, Optional72from dataclasses import dataclass7374logger = logging.getLogger(__name__)7576LEGACY_MODE = False # Constant, never changed at runtime777879@dataclass80class DataRecord:81 id: str82 value: float83 category: str848586def process_data(records: List[DataRecord]) -> dict:87 """Process records and return summary."""88 if not records:89 return {"count": 0, "total": 0.0}9091 total = sum(r.value for r in records)92 temp_result = total * 1.1 # DEAD: unused variable, never read9394 result = {95 "count": len(records),96 "total": total,97 "average": total / len(records),98 }99 return result100101 # DEAD: unreachable code after return102 logger.info("Processing complete")103 notify_downstream(result)104105106def legacy_format_output(data: dict) -> str:107 # DEAD: this function is never called anywhere in the codebase108 """Format output in legacy XML format."""109 parts = []110 for key, value in data.items():111 parts.append(f"<{key}>{value}</{key}>")112 return "<result>" + "".join(parts) + "</result>"113114115def format_output(data: dict) -> str:116 """Format output as JSON string."""117 return str(data)118119120def main():121 if LEGACY_MODE:122 # DEAD: conditional branch that never executes (LEGACY_MODE = False)123 logger.info("Running in legacy mode")124 records = load_legacy_records()125 else:126 records = load_records()127128 result = process_data(records)129 output = format_output(result)130 print(output)131```132133**Static analysis tools by language**:134135| Language | Tool | Command | What It Detects |136|----------|------|---------|-----------------|137| Python | `vulture` | `vulture src/` | Unused functions, variables, imports, classes |138| Python | `autoflake` | `autoflake --check src/` | Unused imports and variables |139| Python | `pylint` | `pylint --disable=all --enable=W0611,W0612 src/` | Unused imports (W0611), unused variables (W0612) |140| JavaScript | ESLint `no-unused-vars` | `eslint --rule 'no-unused-vars: error' src/` | Unused variables, imports, functions |141| JavaScript | `ts-prune` | `ts-prune` | Unused exports in TypeScript |142| Java | IntelliJ / Eclipse | Built-in inspection | Unused declarations, unreachable code |143| Java | SpotBugs | `mvn spotbugs:check` | Dead local stores, unused fields |144| Java | PMD | `pmd check --rulesets category/java/bestpractices.xml` | Unused imports, variables, private methods |145146#### JavaScript Example: Detecting Unused Exports and Functions147148```javascript149// file: src/utils/formatting.js150151// DEAD: exported but never imported anywhere152export function formatLegacyDate(date) {153 const d = new Date(date);154 return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`;155}156157// ACTIVE: imported by 3 modules158export function formatISODate(date) {159 return new Date(date).toISOString().split("T")[0];160}161162// DEAD: exported but never imported anywhere163export function formatCurrency(amount, currency = "USD") {164 return new Intl.NumberFormat("en-US", {165 style: "currency",166 currency,167 }).format(amount);168}169170// DEAD: internal helper, only called by formatLegacyDate (which is also dead)171function padZero(num) {172 return num < 10 ? `0${num}` : String(num);173}174175// ACTIVE: called by formatISODate176function validateDate(date) {177 const d = new Date(date);178 if (isNaN(d.getTime())) {179 throw new Error(`Invalid date: ${date}`);180 }181 return d;182}183```184185#### Java Example: Detecting Unused Code with Call Graph186187```java188// DEAD CODE ANALYSIS:189// 1. LegacyReportGenerator -- class never instantiated or referenced190// 2. UserService.getInactiveUsers() -- method never called191// 3. REPORT_VERSION constant -- never read192// 4. Unused import: java.util.LinkedList193194import java.util.List;195import java.util.ArrayList;196import java.util.LinkedList; // DEAD: unused import197import java.util.Map;198import java.util.stream.Collectors;199200public class UserService {201 private static final String REPORT_VERSION = "2.1"; // DEAD: never read202203 private final UserRepository userRepository;204 private final EmailService emailService;205206 // ACTIVE: called from UserController.getUsers()207 public List<UserDTO> getActiveUsers() {208 return userRepository.findByStatus(Status.ACTIVE)209 .stream()210 .map(this::toDTO)211 .collect(Collectors.toList());212 }213214 // DEAD: never called from any reachable code path215 public List<UserDTO> getInactiveUsers() {216 return userRepository.findByStatus(Status.INACTIVE)217 .stream()218 .map(this::toDTO)219 .collect(Collectors.toList());220 }221222 // ACTIVE: called by getActiveUsers (and would be called by getInactiveUsers)223 private UserDTO toDTO(User user) {224 return new UserDTO(user.getId(), user.getName(), user.getEmail());225 }226}227228// DEAD: entire class is never referenced anywhere in the codebase229public class LegacyReportGenerator {230 public String generateReport(List<UserDTO> users) {231 StringBuilder sb = new StringBuilder();232 sb.append("REPORT\n");233 sb.append("======\n");234 for (UserDTO user : users) {235 sb.append(user.getName()).append("\n");236 }237 return sb.toString();238 }239}240```241242### Step 3: Build and Analyze Call Graphs243244For non-trivial dead code detection, construct a call graph starting from known entry points.245246#### Call Graph Construction Process2472481. **Identify entry points**: main methods, HTTP endpoints, event handlers, scheduled tasks, CLI commands, test methods2492. **Build the forward call graph**: for each entry point, trace all functions/methods that are reachable through direct calls2503. **Identify unreachable nodes**: any function not reachable from any entry point is a candidate for removal2514. **Check for indirect references**: search for reflection, dynamic dispatch, dependency injection, serialization, and string-based method references that static analysis misses252253#### Python Example: Simple Call Graph Builder254255```python256import ast257import os258from collections import defaultdict259from typing import Dict, Set260261262class CallGraphBuilder(ast.NodeVisitor):263 """Build a simple call graph from Python source files."""264265 def __init__(self):266 self.definitions: Dict[str, str] = {} # func_name -> file267 self.calls: Dict[str, Set[str]] = defaultdict(set) # caller -> callees268 self.current_function: str | None = None269270 def visit_FunctionDef(self, node):271 old_function = self.current_function272 self.current_function = node.name273 self.definitions[node.name] = self._current_file274 self.generic_visit(node)275 self.current_function = old_function276277 def visit_Call(self, node):278 if self.current_function and isinstance(node.func, ast.Name):279 self.calls[self.current_function].add(node.func.id)280 self.generic_visit(node)281282 def analyze_file(self, filepath: str):283 self._current_file = filepath284 with open(filepath) as f:285 tree = ast.parse(f.read())286 self.visit(tree)287288 def find_unreachable(self, entry_points: Set[str]) -> Set[str]:289 """Find functions not reachable from any entry point."""290 reachable = set()291 stack = list(entry_points)292293 while stack:294 func = stack.pop()295 if func in reachable:296 continue297 reachable.add(func)298 for callee in self.calls.get(func, set()):299 if callee not in reachable:300 stack.append(callee)301302 all_defined = set(self.definitions.keys())303 return all_defined - reachable304305306# Usage307builder = CallGraphBuilder()308for root, dirs, files in os.walk("src"):309 for f in files:310 if f.endswith(".py"):311 builder.analyze_file(os.path.join(root, f))312313entry_points = {"main", "handle_request", "process_event"}314unreachable = builder.find_unreachable(entry_points)315print(f"Potentially dead functions: {unreachable}")316```317318### Step 4: Handle Special Cases319320Static analysis and call graphs miss certain categories of "hidden" usage. Check each dead code candidate against these patterns before removal.321322#### Hidden Usage Patterns323324| Pattern | How It Hides Usage | Detection Strategy |325|---------|-------------------|-------------------|326| **Reflection** | `getattr(obj, method_name)`, `Class.forName()` | Search for reflection APIs; grep for function names as strings |327| **Dynamic Dispatch** | Plugin systems, strategy patterns via config | Check configuration files, plugin registries |328| **Dependency Injection** | Framework creates instances via config | Check DI container configs (Spring XML, Guice modules) |329| **Serialization** | Fields used only during JSON/XML serialization | Check `@JsonProperty`, `@XmlElement`, `Serializable` annotations |330| **External API** | Public library methods called by external consumers | Check if the code is a library with external dependents |331| **Database Mapping** | ORM fields mapped to DB columns but not accessed in code | Check ORM mappings (Hibernate, SQLAlchemy, Prisma) |332| **Template Engines** | Functions called from HTML/template files | Search template files for function references |333| **Scheduled Tasks** | Methods invoked by cron or task scheduler | Check scheduler configs, `@Scheduled` annotations |334| **Message Handlers** | Methods triggered by message queue consumers | Check message broker configs, `@EventListener` annotations |335336#### Verification Grep Patterns337338```bash339# Search for function name used as a string (reflection risk)340# Replace "myFunction" with the candidate dead function name341grep -r '"myFunction"' --include="*.py" --include="*.js" --include="*.java" src/342grep -r "'myFunction'" --include="*.py" src/343344# Search in configuration files345grep -r "myFunction" --include="*.xml" --include="*.yaml" --include="*.json" .346347# Search in template files348grep -r "myFunction" --include="*.html" --include="*.jinja2" --include="*.ejs" .349350# Search in test files (dead code might be tested but unused in production)351grep -r "myFunction" --include="*.test.*" --include="*_test.*" --include="*Test.java" .352```353354### Step 5: Clean Up Feature Flags355356Feature flags that have been permanently enabled or disabled leave behind dead code paths that should be cleaned up.357358#### Feature Flag Cleanup Process3593601. **Inventory all feature flags**: list every flag, its current state, and when it was last changed3612. **Identify stale flags**: flags that have been in the same state (enabled or disabled) for longer than the team's flag lifecycle policy (typically 30-90 days after full rollout)3623. **Determine the live branch**: for each stale flag, identify which code path is active and which is dead3634. **Remove the dead branch**: delete the code in the inactive branch3645. **Remove the flag check**: replace the conditional with just the live branch code3656. **Remove the flag definition**: delete the flag from configuration, launch darkly, or wherever it is defined366367#### JavaScript Example: Feature Flag Cleanup368369```javascript370// BEFORE: Stale feature flag "new_checkout_flow" has been enabled for 6 months371372import { isEnabled } from "./featureFlags";373374async function processCheckout(cart) {375 if (isEnabled("new_checkout_flow")) {376 // This is the LIVE path (flag has been enabled for 6 months)377 const order = await createOrderV2(cart);378 await processPaymentV2(order);379 await sendConfirmationV2(order);380 return order;381 } else {382 // This is the DEAD path (flag is always enabled, this never executes)383 const order = await createOrder(cart);384 await processPayment(order);385 await sendConfirmation(order);386 return order;387 }388}389390// AFTER: Flag removed, dead branch deleted391392async function processCheckout(cart) {393 const order = await createOrderV2(cart);394 await processPaymentV2(order);395 await sendConfirmationV2(order);396 return order;397}398399// ALSO REMOVE:400// - createOrder, processPayment, sendConfirmation (if only called from dead path)401// - "new_checkout_flow" from feature flag configuration402// - Any tests that specifically tested the old checkout flow403```404405### Step 6: Safe Removal Strategy406407Follow a systematic process to remove dead code safely.408409#### Removal Procedure4104111. **Mark, do not delete**: first, add deprecation annotations or comments to candidate dead code; deploy and monitor for a release cycle4122. **Add logging (optional)**: for uncertain cases, add a log statement inside the suspected dead code and monitor logs for a period; if the log never fires, the code is confirmed dead4133. **Remove in small batches**: delete dead code in focused commits (one logical group per commit) so that any regression can be easily traced and reverted4144. **Run the full test suite**: after each removal, run all tests (unit, integration, end-to-end) and verify nothing breaks4155. **Deploy to staging**: verify the removal in a staging environment before production4166. **Monitor after deployment**: watch error rates, logs, and metrics for 24-48 hours after deploying dead code removal to production417418#### Python Example: Gradual Removal with Logging419420```python421import logging422import warnings423424logger = logging.getLogger(__name__)425426427# Step 1: Mark as deprecated (release N)428@deprecated("This function is believed to be dead code. "429 "If you see this warning, contact the platform team.")430def legacy_format_output(data: dict) -> str:431 # Step 2: Add monitoring432 logger.warning(433 "legacy_format_output was called -- this was believed to be dead code",434 extra={"caller": inspect.stack()[1]},435 )436 # Original implementation437 parts = []438 for key, value in data.items():439 parts.append(f"<{key}>{value}</{key}>")440 return "<result>" + "".join(parts) + "</result>"441442443# Step 3: After monitoring period confirms no calls, remove entirely (release N+1)444# Delete the function and all references445```446447#### Java Example: Safe Removal with @Deprecated448449```java450// Step 1: Mark as deprecated (release N)451/**452 * @deprecated This method is believed to be dead code as of 2024-01.453 * If you encounter this deprecation warning, contact the454 * platform team. Scheduled for removal in release 2024-Q2.455 */456@Deprecated(since = "2024-01", forRemoval = true)457public List<UserDTO> getInactiveUsers() {458 logger.warn("getInactiveUsers() was called -- believed to be dead code");459 return userRepository.findByStatus(Status.INACTIVE)460 .stream()461 .map(this::toDTO)462 .collect(Collectors.toList());463}464465// Step 2: After monitoring confirms no calls, remove in next release466```467468### Step 7: Generate the Dead Code Report469470```471## Dead Code Analysis Report472473### Summary474- **Files analyzed**: {count}475- **Dead code candidates found**: {count}476- **Estimated removable lines**: {count}477- **Estimated size reduction**: {percentage or KB}478479### Findings by Category480| Category | Count | Lines | Confidence |481|----------|-------|-------|------------|482| Unused imports | {n} | {lines} | High |483| Unused variables | {n} | {lines} | High |484| Unused functions/methods | {n} | {lines} | Medium |485| Unused classes | {n} | {lines} | Medium |486| Unreachable code | {n} | {lines} | High |487| Stale feature flags | {n} | {lines} | Medium |488| Obsolete feature code | {n} | {lines} | Low-Medium |489490### Detailed Findings491#### 1. {Dead Code Item}492- **Location**: {file}:{line range}493- **Type**: {category}494- **Confidence**: {high/medium/low}495- **Reason**: {why this is believed to be dead}496- **Hidden usage check**: {reflection: no, DI: no, serialization: no, ...}497- **Recommended action**: {remove / deprecate first / investigate}498499### Removal Plan500- **Phase 1 (safe, immediate)**: {high-confidence items}501- **Phase 2 (deprecate and monitor)**: {medium-confidence items}502- **Phase 3 (investigate)**: {low-confidence items requiring further analysis}503```504505## Best Practices506507- **Start with high-confidence, low-risk removals**: unused imports and unreachable code after return statements are safe to remove immediately; build confidence before tackling uncertain cases508- **Use version control as your safety net**: always commit before removing dead code; if something breaks, you can revert the specific removal commit509- **Remove dead code before adding new features**: cleaning up dead code first reduces confusion and merge conflicts when new feature code is added510- **Do not comment out code instead of deleting it**: commented-out code is still dead code and adds visual noise; rely on version control history to recover deleted code if needed511- **Clean up related artifacts**: when removing a dead function, also remove its tests, documentation, configuration entries, and any supporting helper functions that become dead as a result512- **Automate detection in CI/CD**: configure linters and static analysis tools to flag unused imports and variables on every pull request to prevent new dead code from accumulating513- **Set a regular cleanup cadence**: schedule dead code analysis quarterly or after major feature launches to prevent gradual accumulation514- **Document removal decisions**: in the commit message, briefly explain why the code was determined to be dead and what analysis was performed515516## Common Pitfalls517518- **Removing code used via reflection or dynamic dispatch**: static analysis cannot detect usage through `getattr()`, `Class.forName()`, or plugin systems; always check for string-based references before removing519- **Removing public library APIs**: if the codebase is a library consumed by external projects, "unused" functions may have external callers that are invisible to your analysis; check download/usage metrics and maintain backward compatibility520- **Removing code referenced in configuration files**: functions referenced in Spring XML, Guice modules, routing tables, or scheduler configs appear unused in code but are invoked at runtime521- **Removing ORM-mapped fields**: database column mappings may appear unused in application code but are required for correct serialization and deserialization522- **Deleting "unused" event handlers or webhooks**: code that handles incoming webhooks, message queue events, or scheduled triggers may appear dead because the trigger is external523- **Confusing test-only code with dead code**: helper functions used exclusively in test files are not dead code; they are test utilities524- **Removing code too aggressively in a single commit**: large-scale removal makes it difficult to identify which specific deletion caused a regression; remove in small, focused batches525- **Not monitoring after removal**: even after thorough analysis, some dead code may have hidden callers that only manifest under specific conditions (monthly batch jobs, annual reports, error recovery paths); monitor for a full business cycle after removal