Skip to main content

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()❌ Missed✅ Caught
getattr(obj, "method")()❌ Missed✅ Caught
Plugin hooks (pytest_configure)❌ Missed✅ Caught
Reflection / dynamic imports❌ Missed✅ Caught

When to Use --trace

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

How It Works

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