How I Decide Which Technical Debt to Fix
Every engineering team keeps a list of code it hates. The deployment script nobody dares touch, the service with the four-thousand-line class, the test suite that fails on Tuesdays. Ask a room of engineers about technical debt and you will have a list within minutes, delivered with real feeling. The trap for a manager is treating that feeling as a priority. Dislike is a signal worth listening to, but it is not a measurement.
In this guide I share how I decide which debt to fix now, which to defer with a trigger, and which to accept in writing: the five factors I score on every item, the comparison that settles most arguments, and why the debt a team hates most is sometimes the debt worth keeping.
Dislike is a signal, not a priority
Early in my management career, I treated the list of engineering complaints as a backlog and worked through it in order. The ugliest module got a refactor, then the next ugliest, and I felt productive every week. Two years in, I noticed something uncomfortable: the code looked cleaner, the incident count had not moved, and the systems that kept me up at night were exactly the ones nobody had complained about, because nobody touched them.
I have spent 25 years in infrastructure, from Windows servers and Cisco data centers to large banks, and both seats taught me the same lesson. Debt has a principal, which is what it costs to fix, and an interest rate, which is what it costs to leave alone. Some debt carries brutal interest. Some costs almost nothing, because we will never enter that corner of the system again. A sensible person does not repay every loan the moment they can. They repay the expensive ones first and leave the ones with an effective rate of zero alone.
None of this means dismissing engineers. Frustration is data: it often points at real friction, and it shapes retention and honesty in retrospectives. I treat it as one input. The mistake I made early was treating it as the decision itself. Saying yes to every cleanup request was one of my own recurring failures, collected alongside the rest in my guide to the biggest mistakes I made as an engineering manager.
What I actually score
When a piece of debt lands on my desk, I score five factors. Low, medium, or high is usually precise enough; anything finer is false confidence. The goal is a consistent conversation, not a spreadsheet.
The mental model that ties them together: priority roughly equals risk exposure plus ongoing cost, multiplied by the likelihood we will touch it. Security and compliance deadlines can override the arithmetic. The multiplier is what most debt backlogs never ask. Not how bad is this code, but how often do we actually pay for it.
What happens if nothing changes
The first question I ask about any debt is what happens if we leave it exactly as it is for another year. Most debt answers with nothing much. Some answers with an outage. The second kind goes to the top of the list, even when the code itself is not particularly painful day to day. Operational risk outranks every other factor because the cost of failure is the largest, and because it is the factor engineers are least positioned to see: they feel the debt they work in, not the debt that sleeps.
I have watched teams spend a quarter refactoring cosmetics while a critical service had no tested recovery path. The code looked better afterwards, and the organisational risk had not moved. A customer never notices how elegant your architecture is. They definitely notice an outage, and in a regulated enterprise they may also report it to the regulator.
A deadline whether you like it or not
Security debt gets separated from the stack because it does not follow normal cost-benefit behavior. A messy module might stay messy for years and cost nothing. A known vulnerability gets cheaper to exploit every year, and someone is out there looking for it. The downside is asymmetric: the cost of a breach dwarfs almost any remediation effort, so the usual question of whether the work is worth it answers itself.
That does not mean panic-patching everything a scanner prints. Triage is the job. The same critical CVE is a different problem in an internet-facing component than in an isolated internal tool nobody can reach. Exposure and exploitability matter more than the label, and severity ratings are the beginning of the conversation, not the end of it.
In a regulated enterprise, some of this debt is not really optional. End-of-life software inside an audited scope is an audit finding waiting for a date, and findings arrive with remediation deadlines we do not control. Where we cannot fix immediately, I formalize the exception: compensating controls, a named owner, and an expiry date. Banks already have the machinery for this in their risk registers, and a documented exception is the difference between a governed decision and a surprise. I cover how to present these cases in the language auditors and executives already use in my guide to explaining engineering trade-offs to executives.
One caution from experience: security debt is also where the loudest voice problem is most dangerous in both directions. Panic fixes everything at once and stops the roadmap. Complacency fixes nothing and waits for the finding. The scoring keeps both failure modes away.
The tax on everything
Some debt never causes an outage and never shows up in a scan. It just makes everything slower. Slow pipelines, flaky tests, environments that drift apart, the module where a small change breaks three distant things. The cost is distributed across hundreds of small daily moments, so no single incident justifies action. The sum does. If a team loses thirty minutes a day to tooling friction, that is real engineer-weeks per quarter, spent on nothing anyone would put in a status report.
I measure friction the boring way. During planning I ask how much longer tickets in this area took because of this code, and how confident people are that a change will not break something else. When engineers consistently estimate thirty to fifty percent overhead in one module, that is a tax on every feature that touches it, and it compounds quietly, because it slows down the fixes too.
This is where I think about leverage. Debt work that speeds up every future delivery can out-earn a feature project, which is not a sentence engineers are usually allowed to say in planning. The numbers make it sayable: time per release, incident rate, cycle time. When the case is framed as capacity bought back per month, it competes for budget like anything else, and the evidence comes from the same delivery metrics I use to measure engineering team performance.
Friction is also the factor where the fix is most often cheaper than anyone expected. Stabilizing the ten worst offenders in a flaky suite usually removes most of the pain, and no rewrite is required.
The invisible tax
Some systems are expensive even when nothing is happening. A deprecated runtime we special-case in the infrastructure. A database that needs a human to babysit migrations. A service nobody can safely modify, so every incident drags in the one person who understands it. This cost shows up as toil, and toil is invisible in any plan, because it disguises itself as normal work.
Every enhancement takes longer. Every incident takes longer. Every onboarding takes longer. I have inherited systems where a one-line change needed weeks of analysis, and the team had normalized that as simply how the system is. Naming the cost is simple and effective: roughly one engineer-day per week of toil and incident response is a much stronger sentence in any budget conversation than the code is messy, and it converts cleanly when I translate it for executives.
Reducing maintenance burden is also the most durable return in the debt portfolio. Friction fades when a pipeline speeds up. Toil keeps compounding until something is deleted, automated, or documented, and the cheapest of those three is usually deletion.
The multiplier everyone skips
The factor most teams skip is often the deciding one. Cost alone does not set priority. Cost multiplied by the likelihood we will need to change the system does. Debt on a road we travel every week gets repaid almost as a side effect of normal work. Debt in a quiet corner costs nothing, sometimes for years. It is a messy room we never enter.
Consider two systems with equally tangled internals. System A sits on the critical path of next quarter’s roadmap, changes weekly, and grows with the product. System B is a batch pipeline that has not changed in two years and is scheduled for retirement next quarter. Same ugliness, opposite decisions. I would clean up A and refuse to spend an hour on B, not because B is less ugly, but because we are about to stop paying interest on it permanently.
I run the check explicitly against change history: git, tickets, incidents, and the roadmap. The corollary is the roadmap gate. Before we commit next quarter’s plan, I check it against the debt register. If the plan walks straight through the most debt-laden module we own, that is not a discovery for week six. It is the trigger to fix or fund around the debt before the features land. Roadmaps and debt strategy are the same plan seen from two angles.
The same discipline that separates real emergencies from loud demands applies here. Urgency is a claim someone makes on the roadmap; probability of change is a claim the codebase makes on the future. I cover the first kind of filtering in my guide to setting priorities when everything is urgent.
Fix now, defer, or accept
After scoring, every item lands in one of three buckets. The buckets matter less than the honesty of the reasoning, so each one comes with its own discipline.
The accept bucket is the one engineers struggle with most, and the one managers underuse. Every system that survives long enough carries debt it will never repay. That is not failure. It is maturity, as long as somebody decided it on purpose.
From complaint to decision
None of this needs heavy process. The goal is better decisions, not more ceremony. What holds it together is asking for debt in a particular language, then running the same small loop every time.
The last step is the one managers skip, and it is the one that keeps the system credible. Silence reads as dismissal, and dismissal turns the next genuine warning into a complaint nobody believes. A one-page inventory of the important open debt items, reviewed quarterly, is enough to carry all of this. I resist turning it into more than that, because the ceremony is where these systems go to die.
Three items, one quarter
Here is how three typical items land in one quarter. The examples are composites; the shapes are real.
Notice which item engineers disliked most. The script. And it is the one where I refused to spend capacity. That conversation is the hard part of this job, and it only works when the reasoning is visible and the acceptance is written down with a revisit condition. The flaky suite, which people had learned to live with, was the one actually burning the team every day.
When engineers disagree with the priority
Engineers who point at debt are usually right about the problem and sometimes wrong about the priority. Both halves deserve respect, because the pushback is where most managers either lose trust or build it. The scoring has to survive disagreement, not avoid it.
The same scoring translates upward almost word for word: operational risk becomes business continuity, security becomes audit exposure, friction becomes time to market, maintenance becomes recurring spend, and probability of change becomes this is on our roadmap, so it will hurt us soon. The translation is its own skill, and it is the one that turns debt work from a favor into a funded plan.
How these programs usually fail
Most of these share one root cause: the debt program became a list instead of a set of decisions. Lists grow forever. Decisions close, get revisited on triggers, and free attention for the next one.
Common questions about deciding which technical debt to fix
Q1Is all technical debt bad?
No. Debt taken on deliberately, such as a shortcut to hit a market window, can be a sound trade as long as somebody knows it exists and somebody owns it. The problem is unexamined debt: nobody can name its cost, and nobody ever decided to carry it.
Q2How much capacity should a team spend on technical debt?
There is no universal number. I prefer a steady, modest allocation in every sprint, plus fix-now items tied to real risk or roadmap needs, over occasional debt sprints. Turn it up when the systems you run carry more risk, and down when they are healthy.
Q3How do I quantify technical debt for non-technical stakeholders?
In consequences: incidents, hours lost, delivery delays, audit findings, and rough money. Even approximate numbers with stated assumptions beat adjectives like messy or fragile, because numbers can be challenged and corrected while adjectives just get repeated louder.
Q4What if engineers strongly want to fix something that scores low?
Take it seriously. Recheck the friction and probability-of-change scores, look for a cheap version of the fix, and consider bundling it with feature work that already touches the area. If it still scores low, explain the decision and name what would change it.
Q5When is a full rewrite justified?
Rarely. When a system cannot be changed safely, when the platform is end-of-life with no migration path, or when maintenance cost clearly exceeds the cost of replacement. Even then, incremental replacement usually beats a big-bang rewrite, because the rewrite carries its own defect risk and its own timeline.
Q6What is the best first step for a team drowning in debt?
Write one list, in terms of consequences rather than adjectives, then score it once with the team. The exercise alone usually surfaces two or three items that deserve action and several that deserve a deliberate, documented no.
Q7Should we keep a formal technical debt backlog?
A register, not a graveyard. One page, reviewed quarterly, with owners and triggers, beats an ever-growing ticket list nobody reads. The register is also what lets you show auditors and executives that the debt you carry is carried on purpose.
Q8How do I stop the same debt item being raised every month?
Close the loop. A visible decision with reasoning and a trigger, even a no, ends the monthly re-litigation. Ambiguity is what keeps a complaint alive; a written acceptance with a revisit condition converts it into a managed risk someone owns.
Managing technical debt is not about achieving a clean codebase. It is about spending limited attention where it removes the most risk and friction, and being explicit about what you chose not to spend it on.
A team that can say we know about that, we decided to accept it, here is why, and here is what would change our mind is more mature than one that has fixed everything, and far more mature than one that has ignored everything.
Fix what is on fire and what compounds. Schedule what taxes you every sprint. Accept the rest, in writing, with a trigger for reconsideration. Triage done visibly is one of the most trust-building things an engineering manager does.