How to Find & Fix Technical Debt: A Developer’s Guide

It was meant to take two days. It took two weeks, broke three tests, and now no one wants to touch that code. This is technical debt.
This guide shows how to classify debt, find risky parts using data, prioritize fixes, and make changes safely – while preventing new debt.
Key Points
Classify Your Debt: Not all debt is the same. Separate code, architecture, testing, and other types to choose the right fix.
Make Debt Visible: Use a mix of automated analysis, architectural reviews, and team knowledge to find and measure debt hotspots.
Prioritize: Focus on the 20% of debt items causing 80% of the pain
Fix Incrementally and Safely: Fix in small, safe steps.
Prevent New Debt: Add guardrails with automation, clear standards, and a set budget for ongoing refactoring.
1. How do you quickly assess the technical health of an organization?
The single main thing that lets you know you have technical debt is a noticeable and consistent drop in development velocity. Work that should be simple, like adding a feature or fixing a bug, starts taking much longer. Developers spend time fighting the codebase instead of building. So, the one yes/no question a person should ask themselves to know if they have technical debt is:
Does making a simple change to the code feel disproportionately difficult or slow?
Of course, this is not the only signal, but this friction is the clearest sign of technical debt and directly slows development. Yet, if that’s not enough for you, here are more questions to ask yourself:
Comment
byu/gitcommitshow from discussion
inExperiencedDevs
2. How to find and measure your technical debt?
You cannot fix what you cannot see. You can identify technical debt using static analysis tools, architectural reviews, and team workshops. To measure it, go for tracking complexity, test coverage, and team-reported pain points.
Automated Analysis
Code Scanners: Tools like SonarQube claim they can scan code for bugs, security issues, and code smells, and give clear metrics to track. You should also be able to integrate these into your CI/CD pipeline with a “Quality Gate” to prevent new debt from being introduced.
Key Metrics: Example metrics to quantify your debt:
Cyclomatic Complexity: Measures how complex a function is. It counts decision points. The higher the value, the more complex the logic is, and the code is harder understand, test, and maintain. A value over 9 or 10 is a red flag and you should probably* refactor the code. (* Widely used threshold, but not universal. Many teams tolerate functions with complexity 12–15 if testable and stable. Use as guideline, not absolute rule.)
- Cyclomatic Complexity = 1 + (Number of Decision Points) Decision points are keywords that split the path of your code, such as:
if
/else
while
/for
case
(in a switch statement)- Logical operators like
&&
(AND) and||
(OR)
Example 1: Simple function
function getFullName(firstName, lastName) { return firstName + " " + lastName; // No decisions here }
- Decision Points = 0
- Complexity = 1 + 0 = 1.
Example 2: A little more complex
function getGreeting(name) { if (name) { // <-- 1st decision return "Hello, " + name; } else { return "Hello, stranger."; } }
- Decision Points = 1 (
if
/else
statement) - Complexity = 1 + 1 = 2.
- Cyclomatic Complexity = 1 + (Number of Decision Points) Decision points are keywords that split the path of your code, such as:
Technical Debt Ratio (TDR): Compares the cost to fix your debt versus the cost to rebuild the system. It is calculated as . A TDR of 5% or less is considered healthy (according to the SonarQube guidance; it’s not an industry standard).
Test Coverage: The percentage of your code executed by automated tests. Low coverage = a high risk of introducing regressions when making changes.
Manual & Collaborative Discovery
Comment
byu/Adventurous_Ad22 from discussion
insalesforce
Yet, automated tools don’t have the context. That’s why your team’s experience and the good old “pen & paper method” might be the best way to identify the most painful debt.
Architectural reviews help you see if the system’s design still supports goals like maintainability, security, and scalability. Any gaps you find point to architectural debt.
Hotspot analysis shows where the worst technical debt lives. These are complex parts of the code that change often. Use version control data to spot files with high churn, then check how complex they are.
Finally, team workshops. You map out the system and have the team mark which parts are hardest to work with. The most painful areas become clear, and you get a shared view of where to focus next. Here, Event Storming can help.
3. How to classify technical debt?
Giving it the right name will help you move beyond a generic complaint of “bad code” to a specific problem you can do something about.
What is tech debt? Technical debt refers to the cost of shortcuts in software development that make future changes harder and riskier.
Software expert Martin Fowler classifies debt as either deliberate or inadvertent and prudent or reckless. You might take Prudent and Deliberate debt to meet a deadline with a plan to repay it. Yet, Reckless and Inadvertent debt comes from lack of skill or care. You’ve got to understand whether the root cause is a strategic choice or a need for better training and processes.
Read our related article for a more comprehensive list of technical debt types (that includes both more “technical” and “business” types of the debt). For specifically “tech-tech” types, look at at the table below.
Type | Problem | Example |
---|---|---|
Code Debt | Small, poor implementation choices that reduce code quality and make it harder to read, understand, and change. | Hardcoding config values like API endpoints as “magic numbers” or strings in the code. |
Architectural Debt | Core design flaws that limit system scalability, flexibility, and future changes. | A monolithic architecture where the app is one big unit, and it’s hard to deploy or scale parts of it on their own. |
Testing Debt | The risk of hidden bugs adds up, and it’s hard to make confident changes when testing is weak. | Skipping unit and integration tests to meet a deadline, so there’s no safety net to catch regressions. |
Documentation & Knowledge Debt | Missing or outdated docs mean key info lives only in a few people’s heads (“tribal knowledge”). It’s hard for others to work or take over. | Code lacks comments, README files, or API docs, so future developers have to reverse-engineer what it does. |
Infrastructure & DevOps Debt | Poor infrastructure and process choices make it harder to build, deploy, and run the software smoothly. | There’s no fully automated CI/CD pipeline, so releases are slow, rare, and easy to mess up. |
4. How to prioritize which technical debt to fix first?
You can’t fix everything at once, so treat debt repayment as an investment portfolio. Here, the Pareto rule will apply as well: focus on the 20% of debt items causing 80% of the pain.
Assess each debt item across these four dimensions:
Velocity Impact How much does this debt slow down the delivery of new features on the roadmap? | Business Impact Does the debt directly affect customers or revenue? |
Risk and Stability Does this debt create security vulnerabilities or causes outages? Or maybe it threatens regulatory compliance (like GDPR)? | Morale Impact How much frustration and burnout does this issue cause the team? |
And here are some more aspects to think about from user @vassadar in r/salesforce:
Comment
byu/gitcommitshow from discussion
inExperiencedDevs
5. How to fix existing tech debt?
The prerequisite you need for fixing existing tech debt is automated testing. These tests give quick feedback and strong “yes” to change structure without breaking behavior.
Micro-Level Fixes: Code Refactoring
For localized code debt, use disciplined refactoring techniques. Coined by Martin Fowler, refactoring alters internal structure without changing external behavior.
Extract Method: Take a fragment of code from a long method and move it into its own new method with a descriptive name. This makes the original method shorter and easier to understand.
Replace Magic Number with Symbolic Constant: Find any raw, unexplained numbers in your code (e.g.,
if (user.role == 3)
) and replace them with a named constant (e.g.,if (user.role == ADMIN_ROLE)
). This makes the code self-documenting.Replace Nested Conditional with Guard Clauses: Instead of deeply nesting
if
statements, check for all the error conditions or edge cases at the very beginning of a function andreturn
early. This flattens the code and makes the main, “happy path” logic easier to read.
Macro-Level Fixes: Architectural Modernization
For systemic issues like a monolith, you need architectural patterns, like the Strangler Fig Pattern. This is the best pattern for incrementally replacing a legacy system. You build new functionality as separate services and place a routing layer in front of the old monolith. Over time, the new services “strangle” the old system until it has no responsibilities left and can be safely left. This way you avoid a high-risk “big bang” and can deliver value incrementally.
6. How to manage and prevent technical debt?
Comment
byu/StudyMelodic7120 from discussion
indotnet
Prevention is always cheaper than remediation, so these guardrails will help you save both your nervous system and project’s budget.
Automate Quality Gates: Integrate static analysis and testing into your CI/CD pipeline. Quality Gate is an automated check that fails the build if it doesn’t meet quality criteria, like minimum test coverage or zero new critical bugs. This makes quality non-negotiable.
Adopt the “Boy Scout Rule”: Be a good scout and “always leave the code you’re editing a little better than you found it”.
Modernize with Infrastructure as Code (IaC): Manage servers, databases, and networks as code using tools like Terraform or CloudFormation. This keeps environments consistent, repeatable, and under version control.
Foster Psychological Safety: Create a culture where developers can speak up, ask questions, and push for better practices without fear. If mistakes get punished, people just get better at hiding them – and hidden bugs age like milk, not wine.
Conclusion
Technical debt is inevitable, but it is controllable. Classify debt, make it visible with data, prioritize by impact, and fix it in safe steps to regain control.
Ultimately, managing technical debt is a reflection of your team’s culture. The best strategy is prevention, but if it didn’t work for you, get in touch with us. We help companies get rid of legacy code.
Find & Fix Technical Debt: FAQ
What specific things should you look for when identifying technical debt?
You need to identify parts of the system that weren’t built to scale, don’t follow best practices, or are redundant and no longer used but have been kept “just in case.”
Who is responsible for identifying and planning the response to technical debt?
The responsibility falls on the internal team working alongside the business. Together, they must identify the problem areas and create a plan to “remove, improve, rearchitect, or rebuild” them.
What are two immediate actions you should AVOID after discovering technical problems?
You should not panic, as the situation presents a great opportunity. Secondly, you should not stop feature development to start tackling technical debt (in general; if critical security/stability debt exists, sometimes features should be paused to pay the debt off first).
What is the initial process for handling technical debt?
The first step is to list all technical debt items and then prioritize them based on specific criteria. Items where the reward for fixing them is good relative to the risk should be dealt with earlier.
What types of issues are considered high-priority?
Any debt that exposes stability or security concerns should be a high priority (e.g., an external service client missing a circuit breaker or an API lacking a rate limiter).
How does test coverage influence how you prioritize technical debt?
An automated test suite reduces the risk of breaking functionality when addressing technical debt, making it safer to prioritize that component. Code without tests should generally have tests added first before debt is tackled.
When should you decide to leave 'ugly' code as it is?
If a piece of code is ugly but very stable and used in multiple places, it’s better to leave it alone. However, if that code is changed often or may be a risk to maintainability, security, or scalability, then improving it for the sake of maintainability is worthwhile.
How do you know when it's time to re-architect a component?
It’s time for a re-architecture when you monitor a simple, non-scalable implementation and find that it is reaching its limit—it can’t meet business or technical needs, such as scale, performance, compliance, or maintainability.
What is a good strategy for scheduling technical debt work?
A practical strategy is to inject a few technical debt tickets into each sprint. This ensures that the debt is dealt with periodically and doesn’t build up. The “cool down” period between sprints is also a good time to perform this work.
What are the key principles for building a healthy codebase?
Consistency and encapsulation are key. It’s also crucial to build everything to be as constrained as possible, using proper constructors (ctors) and carefully considering which parameters are required versus optional.