← Build log

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:

  1. Confidence builds over time - Each successful cleanup makes the next one easier
  2. Your codebase becomes more maintainable - Less cruft means clearer dependencies
  3. Build times improve - Fewer files to process, smaller bundles
  4. Mental clarity increases - Less cognitive load from irrelevant code
  5. 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.