Finding Dead Code Before It Ships
By CKB Team
Dead code is a silent tax on every codebase. It confuses developers, bloats bundles, and creates false positives in searches. CKB helps you find it before it ships.
The Dead Code Problem
Code becomes "dead" in several ways:
- Unused functions - Written for a feature that was removed
- Unreachable branches - Conditions that can never be true
- Deprecated APIs - Old versions kept for compatibility that no one uses
- Copy-paste remnants - Duplicated code where the original was deleted
Traditional static analysis catches some of this, but misses code that could be called but isn't in practice.
CKB's Two-Pronged Approach
CKB combines static and dynamic analysis:
- Static analysis (
findDeadCode) - Code with no callers in the codebase - Runtime analysis (
getObservedUsage) - Code that exists but is never executed
Together, they give you confidence that code is truly dead.
Finding Statically Dead Code
User: "Find unused functions in the payment module"
AI: [calls findDeadCode for internal/payment]
"Found 7 potentially unused functions:
High confidence (no callers):
- formatLegacyReceipt() in receipt.go:145
- validateOldChecksum() in validation.go:89
- migrateV1Transaction() in migration.go:34
Medium confidence (only test callers):
- mockPaymentProvider() in helpers.go:23
- createTestTransaction() in helpers.go:67
Low confidence (reflection/dynamic calls possible):
- handleWebhookV1() in webhooks.go:123
- processLegacyRefund() in refund.go:78"
Understanding Confidence Levels
CKB assigns confidence based on how the code might be called:
High confidence - No references anywhere. Safe to remove.
Medium confidence - Only called from tests. The tested code might be dead, or tests might be covering internal helpers.
Low confidence - No static callers, but the function name suggests it might be called dynamically (reflection, webhooks, event handlers).
Checking Runtime Usage
Static analysis has limits. A function might have callers but those callers are also dead. Or code might be called via configuration or reflection.
getObservedUsage checks against runtime telemetry (if configured):
User: "Is handleWebhookV1 actually used in production?"
AI: [calls getObservedUsage for handleWebhookV1]
"Runtime analysis for handleWebhookV1:
Last 30 days:
- Invocations: 0
- Last seen: 2025-08-14 (5 months ago)
Caller analysis:
- webhookRouter calls this for 'v1' webhook type
- No v1 webhooks have been registered since migration
Verdict: Safe to remove. V1 webhook format is deprecated
and no active integrations use it."
Setting Up Runtime Tracking
CKB can integrate with your observability stack:
// .ckb/config.json
{
"observedUsage": {
"enabled": true,
"source": "datadog", // or "prometheus", "custom"
"functionMetric": "function.invocations",
"lookbackDays": 30
}
}
Without runtime integration, CKB uses git history heuristics (last modification date, recent callers) to estimate usage likelihood.
Workflow: Cleaning Up Dead Code
Step 1: Scan the Codebase
User: "Find all dead code in our codebase"
AI: [calls findDeadCode with full scope]
"Dead code analysis complete.
Summary:
- 23 unused functions
- 8 unused types/structs
- 12 unused constants
- 156 unused imports (auto-fixable)
Estimated savings: ~2,400 lines of code (3.2% of codebase)
Would you like me to break this down by module?"
Step 2: Prioritize by Impact
User: "Which dead code should I remove first?"
AI: [analyzes dead code by impact]
"Priority ranking:
1. internal/legacy/ - 890 lines, entire module unused
Impact: Reduces bundle size, removes confusing old patterns
Risk: Low - no callers, clear deprecation comments
2. pkg/utils/deprecated.go - 234 lines
Impact: 12 functions that duplicate stdlib
Risk: Low - wrapper functions with modern replacements
3. internal/payment/v1/ - 456 lines
Impact: Old payment processing, migrated 8 months ago
Risk: Medium - verify no v1 API consumers remain
4. Scattered functions - 820 lines across 34 files
Impact: Incremental cleanup
Risk: Varies - review individually"
Step 3: Verify Before Removing
User: "Is it safe to delete internal/legacy/?"
AI: [calls prepareChange with delete]
[calls getObservedUsage for all exports]
"Safety analysis for deleting internal/legacy/:
Static analysis: No internal callers ✓
Runtime analysis: Zero invocations in 90 days ✓
External consumers: None (internal package) ✓
Test dependencies: 3 test files import this module
Action required:
- Delete internal/legacy/
- Delete internal/legacy_test.go
- Update test files: payment_test.go, auth_test.go, user_test.go
This is safe to remove."
Step 4: Clean Up
User: "Generate a PR description for removing the legacy module"
AI: "## Remove unused legacy module
### Summary
Removes `internal/legacy/` which has been unused since the v2 migration.
### Changes
- Delete `internal/legacy/` (890 lines)
- Delete associated tests (156 lines)
- Update 3 test files that imported legacy helpers
### Verification
- CKB static analysis: No callers
- Runtime telemetry: Zero invocations in 90 days
- Manual review: Confirmed all functionality migrated to v2
### Impact
- Reduces codebase by ~1,000 lines
- Removes confusing deprecated patterns
- Slightly faster build times"
Common Dead Code Patterns
Feature Flags Never Removed
User: "Find feature flags that are always on"
AI: [searches for feature flag patterns]
[calls getObservedUsage]
"Found 4 feature flags that are always enabled:
- ENABLE_NEW_CHECKOUT (enabled 2024-03-15, 22 months ago)
- USE_V2_AUTH (enabled 2024-08-01, 17 months ago)
- FAST_SEARCH (enabled 2025-01-01, 12 months ago)
- NEW_DASHBOARD (enabled 2025-06-01, 7 months ago)
The first three are safe to remove - the 'old' code paths
are completely dead."
Compatibility Shims
User: "Find backwards compatibility code we can remove"
AI: [searches for compatibility patterns]
"Found 8 compatibility shims:
Safe to remove (no consumers):
- convertV1Response() - V1 API deprecated 18 months ago
- legacyDateFormat() - Old mobile app discontinued
- shimOldWebhook() - Webhook v1 migration complete
Need verification:
- supportOldBrowser() - Check analytics for IE11 traffic
- legacyEncryption() - May affect stored data"
Prevention: Catching Dead Code Early
Pre-commit Hook
# .ckb/hooks/pre-commit
ckb check dead-code --staged --fail-on-new
CI Integration
# .github/workflows/dead-code.yml
- name: Check for dead code
run: |
ckb findDeadCode --format=json > dead-code.json
if [ $(jq '.high_confidence | length' dead-code.json) -gt 0 ]; then
echo "New dead code detected!"
exit 1
fi
Results
Teams using CKB for dead code detection typically find:
- 2-5% of their codebase is unused
- 30% faster onboarding (less confusing code)
- Smaller bundle sizes
- Faster test runs (fewer dead code paths to cover)
Links: