Skip to main content

Dead Code Detection

The Hidden Cost of Dead Code

Dead code isn't just clutter. It's active technical debt that:

  • Slows Development - Developers read and reason about code that does nothing
  • Hides Bugs - Unused code paths can mask logic errors and security issues
  • Increases Attack Surface - Vulnerable code that's "not used" can still be exploited
  • Bloats Bundles - Unused imports increase load times and memory usage

The problem? Removing code is scary. What if it's actually used somewhere you didn't check?


How Skylos Finds Dead Code

Skylos builds a complete reference graph of your codebase, then identifies definitions with zero references.

What Gets Detected

CategoryExampleDetection Method
Unreachable functionsdef helper(): ... never calledNo call references found
Unused importsimport json but json never usedNo name references found
Unused classesclass OldModel: ... never instantiatedNo instantiation or inheritance
Unused variablesresult = compute() but result never readAssigned but never referenced
Unused parametersdef fn(a, b): return aParameter b never used in body

The Confidence System

Not all "unused" code is actually dead. A helper function might be:

  • Called dynamically via getattr()
  • Used by a framework implicitly
  • Part of a public API

Skylos assigns a confidence score (0-100) to each finding, so you can filter out uncertain results.

Confidence Penalties

PatternPenaltyReason
Private name (_foo)-80Convention for internal use
Dunder (__str__)-100Called implicitly by Python
Underscore var (_)-100Intentionally unused
In __init__.py-15Often public API re-exports
Framework decorator-40Called by framework
Dynamic module-40May use getattr()
Test-related-100Called by test runner

Using Confidence Threshold

# Default: only findings with ≥60% confidence
skylos .

# Include more uncertain findings
skylos . --confidence 40

# Only high-confidence findings
skylos . --confidence 80

Framework Awareness

Skylos understands that framework code is called implicitly:

# Not flagged - Django calls this via URL routing
def user_detail(request, pk):
return render(request, 'user.html', {'user': User.objects.get(pk=pk)})

# Not flagged - Signal receiver
@receiver(post_save, sender=User)
def create_profile(sender, instance, **kwargs):
Profile.objects.create(user=instance)

# Not flagged - Class-based view methods
class UserView(View):
def get(self, request):
return HttpResponse("Hello")

Smart Tracing (Runtime Analysis)

Static analysis can't catch everything. When code is called dynamically via getattr(), visitor patterns, or reflection, Skylos may flag it as unused.

The solution: Run your tests with call tracing enabled.

skylos . --trace

This:

  1. Runs your test suite with sys.settrace() enabled
  2. Records every function that was actually called
  3. Uses that data to eliminate false positives

What Gets Captured

PatternStatic AnalysisWith --trace
visitor.visit(node)visit_FunctionDef()MissedCaught
getattr(obj, "method")()MissedCaught
Plugin hooks (pytest_configure)MissedCaught
Reflection / dynamic importsMissedCaught

When to Use --trace

Use It:

  • Projects with visitor patterns (AST, CST)
  • Plugin architectures
  • Heavy use of getattr() / reflection
  • Many false positives from static analysis

Skip It:

  • Simple codebases with direct calls
  • No test suite available
  • CI where speed matters (tracing adds overhead)

How It Works

tip

Tip: Commit .skylos_trace to your repo if your test suite is stable. Then skylos . will use it automatically without re-running tests.


Comparison: Why Not Just Use Your IDE?

FeatureIDE "Unused" WarningSkylos
Cross-file analysis❌ Single file✅ Entire codebase
Framework awareness✅ Django, Flask, FastAPI, Pydantic
Confidence scoring✅ Filter uncertain findings
CI/CD integration✅ Block PRs, generate reports
Batch removal✅ Interactive selection
Import trackingBasic✅ Resolves re-exports

Safe Removal Workflow

1. Scan with high confidence

Start with findings you can trust:

skylos . --confidence 80

2. Review in interactive mode

Select what to remove:

skylos . -i --dry-run

3. Remove or comment out

# Delete selected items
skylos . -i

# Or comment out (safer)
skylos . -i --comment-out

4. Run tests

Verify nothing broke:

pytest

Comment-Out Mode

Instead of deleting, Skylos can comment out code with a marker:

# Before
def unused_helper():
return "I'm not used"

# After --comment-out
# SKYLOS DEADCODE: def unused_helper():
# SKYLOS DEADCODE: return "I'm not used"

Search for SKYLOS DEADCODE later to permanently remove or restore.


Output Formats

─────────────────── Unreachable Functions ───────────────────
# Name Location
1 unused_helper utils.py:42
2 legacy_processor core/processing.py:128

────────────────────── Unused Imports ───────────────────────
# Name Location
1 json api/views.py:3
2 Optional models.py:1

Real-World Impact

Case Study: E-commerce Platform

A 200K LOC Python codebase ran Skylos and found:

  • 47 unused functions (3,200 lines of dead code)
  • 156 unused imports (faster startup, smaller bundles)
  • 12 unused classes (legacy models never migrated)

Result: 15% reduction in codebase size, faster CI builds, easier onboarding for new developers.


Next Steps