The Solo Builder's Guide to Safe Tech Debt Cleanup: How to Eliminate Cruft Without Breaking Everything
Posted: June 21, 2025
Yesterday I wrote about our 4-hour tech debt cleanup that changed everything. Today I want to dive deeper into the how - the specific strategies and safety nets that let you aggressively clean up years of accumulated cruft without destroying your working application.
As solo builders, we don’t have the luxury of dedicated QA teams or extensive code review processes. One wrong move can take down our entire business. But that doesn’t mean we should live with mounting tech debt forever. Here’s how to clean house safely.
The Golden Rule: Git is Your Safety Net
Before touching a single line of code, internalize this: Git is not just version control, it’s your undo button for catastrophic mistakes.
The Cleanup Branch Strategy
Never, ever do tech debt cleanup on your main branch. Here’s my standard workflow:
# Start clean
git checkout main
git pull origin main
# Create a dedicated cleanup branch
git checkout -b tech-debt-cleanup-june-2025
# Make your changes in small, logical commits
git add deleted-files/
git commit -m "Remove unused recording controller (1,291 lines)"
git add updated-docs/
git commit -m "Update docs to reflect new architecture"
# Test everything works
npm run start:gui # Full test suite + build + app launch
# Only merge when you're confident
git checkout main
git merge tech-debt-cleanup-june-2025
The key insight: Each commit should be a complete, testable unit. If something breaks, you can git revert that specific commit without losing all your other cleanup work.
Commit Message Discipline
Your future self will thank you for descriptive commit messages:
# Bad
git commit -m "cleanup"
# Good
git commit -m "Remove dual-mode CLI architecture (617 lines)
- Delete src/electron/settings-manager.ts
- Remove CLI-specific config handling
- Update 15 documentation files
- All tests passing, GUI functionality verified"
When you’re debugging a broken build at 2 AM three months from now, that detail will save your sanity.
The Rename-Before-Delete Strategy
Here’s where most people mess up: they delete files and immediately lose the ability to recover if something breaks. Always rename first, delete later.
For Code Files
Instead of:
rm src/old-feature.ts # Gone forever if you missed a dependency
Do this:
mv src/old-feature.ts src/old-feature.ts.DELETED
# Test everything still works
# If it breaks, just rename it back
For Assets and Images
We recently cleaned up 1.4MB of unused images in VoiceCatcher. Here’s how we did it safely:
# Don't delete - rename with .unused suffix
mv img-archive/old-icon.png img-archive/old-icon.png.unused
# Test the build
npm run build:compile
# If it breaks, restore immediately
mv img-archive/old-icon.png.unused img-archive/old-icon.png
This saved us when we discovered that microphone-icon.svg was actually needed by the build system, even though no code directly referenced it. One quick rename and we were back in business.
Create Restoration Scripts
For large cleanups, automate the restoration process:
#!/bin/bash
# restore-images.sh
echo "Restoring all .unused files..."
for file in img-archive/*.unused; do
if [ -f "$file" ]; then
original="${file%.unused}"
mv "$file" "$original"
echo "Restored: $(basename "$original")"
fi
done
Having this script ready removes the fear factor from aggressive cleanup.
The Progressive Deletion Approach
Don’t try to clean everything at once. Use a systematic, escalating approach:
Phase 1: Obviously Dead Code
- Files with no imports/exports
- Commented-out code blocks
- Old backup files (
*.backup.js) - Temp files and build artifacts
Risk Level: Low - These are usually safe to remove immediately.
Phase 2: Unused Features
- Entire modules/components with no references
- Old API endpoints
- Deprecated configuration options
Risk Level: Medium - Rename first, test thoroughly.
Phase 3: Architectural Changes
- Removing entire subsystems
- Consolidating duplicate functionality
- Major refactoring
Risk Level: High - Small commits, extensive testing, keep restoration paths open.
Testing Your Safety Net
Before starting any cleanup, verify your testing and build process catches breakage:
# Does your build fail fast when dependencies are missing?
npm run build:compile
# Do your tests actually run and catch regressions?
npm run test:unit
npm run test:integration
# Does your app start and function correctly?
npm run start:gui
If any of these don’t work reliably, fix them first. You can’t safely clean up without a reliable way to detect breakage.
When to Stop: The Solo Builder’s Dilemma
This is the hardest part. Tech debt cleanup can become addictive - there’s always more cruft to remove. But as a solo builder, your time is finite and your business needs to keep running.
Green Light Indicators (Keep Going)
- High-impact, low-risk removals (dead code with no dependencies)
- Immediate benefits (faster builds, clearer codebase)
- You’re in flow state and making steady progress
- Tests are passing consistently
Yellow Light Indicators (Proceed with Caution)
- Medium-risk changes that require careful testing
- Diminishing returns (spending hours to save kilobytes)
- You’re getting tired and making sloppy decisions
- Build times are getting longer due to frequent testing
Red Light Indicators (Stop Now)
- Breaking changes that require extensive refactoring
- You’re guessing about whether code is safe to remove
- Multiple systems are interconnected in unclear ways
- Business pressure to ship new features
- You’ve been at it for more than 4-6 hours in one session
The 80/20 Rule for Tech Debt
In our VoiceCatcher cleanup, we got 80% of the benefits (2,898 lines removed, major architecture simplification) in the first 4 hours. The remaining 20% of potential cleanup would have taken exponentially longer and carried much higher risk.
Know when to declare victory and move on.
Red Flags: When to Abort the Mission
Sometimes you discover that your “simple cleanup” has uncovered deeper architectural problems. Here are the warning signs to stop and reassess:
Dependency Hell
// You want to delete this file, but discover it's imported by:
import oldThing from './old-feature'; // 47 different files
If removing one file requires touching dozens of others, you’ve stumbled into a refactoring project, not a cleanup task.
Configuration Chaos
// You find settings that control multiple unrelated systems
{
"enableOldMode": true, // Used by 5 different modules
"legacySupport": false, // Unclear what this affects
"compatMode": "auto" // No documentation
}
Configuration cleanup often reveals architectural debt that’s too risky to tackle in a cleanup session.
Test Failures You Don’t Understand
✓ 122 tests passing
❌ 1 test failing: "should prefer release binary over debug binary"
If you can’t quickly understand why a test is failing, you’re probably dealing with deeper issues than simple cleanup.
The Compound Benefits
Here’s why this methodical approach pays off:
- Confidence builds over time - Each successful cleanup makes the next one easier
- Your codebase becomes more maintainable - Less cruft means clearer dependencies
- Build times improve - Fewer files to process, smaller bundles
- Mental clarity increases - Less cognitive load from irrelevant code
- New features are easier to add - Cleaner architecture provides better foundation
Tools That Help
Git Aliases for Common Operations
# .gitconfig
[alias]
cleanup = checkout -b tech-debt-cleanup-$(date +%Y%m%d)
safe-commit = commit -m
test-build = !npm run build:compile && npm run test:unit
Scripts for Common Tasks
# find-unused-files.sh
find src/ -name "*.ts" -exec grep -l "import.*{}" {} \; | \
xargs grep -L "export" | \
head -10 # Show first 10 potentially unused files
IDE Extensions
- Unused imports detection (TypeScript Hero, Auto Import)
- Dead code highlighting (SonarLint)
- Dependency visualization (Dependency Cruiser)
The Mindset Shift
The biggest change isn’t technical - it’s psychological. Stop thinking of tech debt cleanup as “risky refactoring” and start thinking of it as “systematic archaeology.”
You’re not changing how things work, you’re just removing the things that don’t work anymore. With proper safety nets (git branches, rename-before-delete, comprehensive testing), the risk approaches zero while the benefits compound.
Your Turn
Start small. Pick one obviously dead file in your codebase. Rename it with a .DELETED suffix. Run your tests. If everything passes, commit the change. If something breaks, rename it back and investigate.
That’s it. You’ve just completed your first safe tech debt cleanup.
The compound effect of these small, safe cleanups will transform your codebase over time. And unlike massive refactoring projects that never seem to finish, each cleanup session leaves you with a working, slightly better application.
The best time to clean up tech debt was when you first created it. The second best time is now.
Building VoiceCatcher has taught me that sustainable solo development isn’t about avoiding tech debt - it’s about managing it systematically and safely. Want to see more behind-the-scenes content like this? Consider supporting my work on Patreon.