Make the debt visible
You can't reduce what you can't see. Inventory the debt with a code and architecture review, plus input from the engineers who feel it every day. Then track it as real, estimated backlog items — tagged and ranked — rather than leaving it as tribal knowledge that never competes for prioritization.
Prioritize by business impact, not preference
Not all debt is worth paying down. Rank each item by how much it slows delivery, raises risk, or drives defects — weighted by how often that code actually changes. High-churn, high-pain areas come first; stable code you rarely touch can usually stay as-is.
Allocate steady capacity
Reserve a consistent share of every cycle — commonly 15–20% — for paydown, so it competes fairly with features instead of never happening. Consistency beats the occasional "debt sprint," which tends to slip the moment a deadline appears.
Refactor incrementally
Replace risky areas piece by piece using patterns like Strangler Fig and Branch by Abstraction, adding test coverage as you go, so production keeps running and value shows up early. Avoid big-bang rewrites — they carry the most risk and rarely finish on schedule.
Prevent new debt from replacing it
Reduction only sticks if the inflow slows. Coding standards, meaningful code review, automated tests, and a "leave it better than you found it" habit keep new debt from accumulating as fast as you pay the old debt down.