If I look back at every engineering team I’ve worked on, “we need to deal with our tech debt” is probably the most repeated sentence of all. It shows up in retrospectives, in planning, and near the top of every engineering survey I’ve ever answered. And yet, if you ask five engineers what tech debt actually is, you’ll get five different answers, ranging from “everything in the codebase I don’t understand” to “a deliberate trade-off we made to ship on time”.
That lack of a shared definition is exactly why it’s so frustrating. When anything that slows us down can be labelled “debt”, the word stops meaning anything. It also turns into a bottomless backlog (debt has to be repaid, so it keeps piling up), and a source of tension, because engineers feel pushed to take on debt they never agreed to and then don’t feel empowered to pay it back.
I want to share the approach I’ve settled on over the years to tame this mess. It’s not a tool or a ceremony, it’s mostly about naming things properly and being explicit.
Name it to tame it
The first step is to stop using “tech debt” as a catch-all. I find it much more useful to split the idea into three distinct concepts.
Technical Drag is the umbrella term: everything in our code and architecture that slows us down. That’s the only property all of these things share, their effect on our productivity. You can’t act on “drag” directly, but giving it a name lets us talk about the whole problem before we slice it up.
Technical Debt is the result of a conscious, explicit decision, with a repayment plan attached to it. This is the key part: real debt is chosen, not discovered. You take it on in exchange for something valuable (time to market, time to learn, a cheaper experiment). And like with any kind of debt (like financial debt), when accepting it, you must have a plan for how and when you’ll pay it back. Because Engineering and Product are “two halves of the same brain”, debt should never be taken on unilaterally by Engineering. More on this later.
Technical Cruft is everything that got in the way without a deliberate decision. It can be mediocre code or architecture, a solution that was fine for last year’s scale but not today’s, a previously sensible design that no longer fits the business, or plain software entropy (CVEs, a dependency that won’t upgrade cleanly, a platform change you didn’t ask for). Nobody chose cruft, it just accumulated.
Once you have this shared vocabulary, you can do three things that were impossible before: build an inventory so you can measure how much drag exists (and whether it’s growing or shrinking), attach repayment plans to the debt, and find the bottlenecks that are actually hurting your delivery. Let’s go through each.
Build an inventory of technical drag
Systems tend to improve when you observe them. And even when they don’t improve on their own, making them observable is what lets you decide where to act.
So the first practical move is to make technical drag tangible. The simplest way I know is to create issues (GitHub, GitLab, whatever you already use) and label them tech-drag. That’s it. Every annoying slowdown, every “ugh, this again” moment, becomes a real, countable thing instead of a vague feeling.
Later, once you’ve had the cycles to actually look at an issue, you can add a second label: either tech-debt or tech-cruft (never both). The distinction matters, and it has a rule attached:
tech-debt once it has a repayment plan. Debt is explicit by definition, so an issue without a plan is not debt yet, it’s just drag you haven’t analysed.This sounds almost too simple, but the act of labelling is what turns “the codebase is a mess” into “we have 14 pieces of drag, 3 of them are debt with plans, and the trend is flat this quarter”. That second sentence is something you can actually manage.
Create repayment plans for technical debt
Here’s the part that changes the most about how a team works: debt is not something Engineering gets to take on by itself.
Every decision to take on debt needs to be explicit and aligned with Product. Engineering Managers, and Product, need to understand the value of what’s being traded and what it’ll cost later. When you decide to take on debt in exchange for some benefit (shipping sooner, learning faster), you write a repayment plan.
A repayment plan is really just an agreement (a kind of “contract”) with the other disciplines: a commitment to reserve capacity to undo the side effects of the debt at a known point in the future. I document each one as an issue, link it back to the reason the debt was taken (the feature, the product doc), label it tech-debt-repayment, and give it a due date. New debt also gets announced alongside the feature it enabled, when announcing that feature completion to the company.
What I love about this process is the ripple effects it has, beyond just keeping a tidy backlog:
- It creates a real decision point with your peers about whether the debt is even worth taking. Plenty of times, the act of writing down the repayment forces the conversation that makes you go “actually, let’s not”. The cheapest debt is the one you never take on.
- It builds shared ownership of the system’s health. You explore quickly, invest in the bets that win, and (this is the important bit) you delete the ones that don’t instead of paying interest on them forever.
That last point deserves emphasis. If we take on debt to build something that turns out not to move the needle, and then we keep it and pay down its debt, we’re not being rational. It’s completely normal (expected, even) for a lot of experiments to fail. We should be deleting far more code than we tend to, because deleting is almost always cheaper than maintaining.
Find the bottlenecks
Once you have an inventory and explicit repayment plans, you can start reasoning about “interest rates”. The more a given piece of drag slows you down, the higher its interest, and the more you gain by cancelling it.
This is where the debt metaphor really earns its keep. Debt and cruft sitting in the critical path of your roadmap can create a bottleneck that drags your delivery towards zero. That’s where your attention belongs.
And the opposite is just as important: not all drag is worth fixing. Debt that sits well outside the critical path has a low interest rate, and paying it off is often a poor use of time, unless it’s generating real, recurring cost elsewhere (think incidents at 3am, or a steady drip of support tickets). I label the issues that sit on the critical path as bottleneck, so it’s obvious where the leverage is.
The interest rate is not fixed, either. It moves with whatever is on your roadmap right now. A piece of drag that’s irrelevant this quarter can become your worst bottleneck the moment a new initiative touches that part of the system.
A decision flow for taking on debt
The hardest moment to get right is the one where you’re about to take on new debt. I find a simple flow helps keep everyone honest: it forces the debt to be visible, explicit, and genuinely worth it.
Here’s how I think about it when a new initiative lands. Notice how the decision keeps bouncing between Product and Engineering, never one side alone:
flowchart TD
A([New initiative]) --> B[Coarse effort estimation]
B --> C{Is the effort acceptable?}
C -->|Yes| Z([Do it])
C -->|Too big| D[Brainstorm to reduce scope]
D --> E{Can we reduce the scope?}
E -->|Yes| Z
E -->|No| F[Estimate effort taking on tech debt]
F --> G[Create a repayment plan]
G --> H{Is the trade-off acceptable?}
H -->|No| X([Abort the initiative])
H -->|Yes| I[Add the repayment plan to the backlog]
I --> J[Document the repayment plan]
J --> Z
subgraph Legend [Who owns each step]
direction LR
LP[Product]:::product
LE[Engineering]:::engineering
LM[Multidisciplinary]:::multi
end
class A,D multi
class B,F,G,I engineering
class C,E,H,J product
classDef product fill:#ffd54f,stroke:#ffd54f,color:#212936;
classDef engineering fill:#139cd4,stroke:#139cd4,color:#fffbf6;
classDef multi fill:#ec632b,stroke:#ec632b,color:#fffbf6;Click on diagram to enlarge
The same logic applies while you’re executing an initiative and reality starts to drift from the plan. The question is always the same: can we stay aligned, can we reduce scope, and if not, is taking on debt an acceptable, documented trade-off?
flowchart TD
Z([Do it]) --> PlanCheck{Aligned to plan?}
PlanCheck -->|Yes| Done{Initiative done?}
PlanCheck -->|No| Mis{Acceptable deviation?}
Mis -->|Yes| Done
Mis -->|No| Scope{Can we reduce scope?}
Scope -->|Yes| Done
Scope -->|No| DebtPlan[Estimate effort taking on tech debt]
DebtPlan --> Repay[Create a repayment plan]
Repay --> DebtCheck{Can we accept the debt?}
DebtCheck -->|No| Mis
DebtCheck -->|Yes| Backlog[Add the repayment plan to the backlog]
Backlog --> Doc[Document the repayment plan]
Doc --> Done
Done -->|No| PlanCheck
Done -->|Yes| End([Done])
class Done multi
class PlanCheck,DebtPlan,Repay,Backlog engineering
class Mis,Scope,DebtCheck,Doc product
classDef product fill:#ffd54f,stroke:#ffd54f,color:#212936;
classDef engineering fill:#139cd4,stroke:#139cd4,color:#fffbf6;
classDef multi fill:#ec632b,stroke:#ec632b,color:#fffbf6;Click on diagram to enlarge
These diagrams are guidance, not gospel. The point isn’t to follow the boxes exactly, it’s to keep debt visible and explicitly agreed upon. Your team will probably find its own way of being explicit that fits your process better, and that’s perfectly fine.
This whole approach leans heavily on Engineering and Product trusting each other and talking openly, which is the same foundation I wrote about in the “Ship, Show, Ask” git strategy. Mature teams make these trade-offs out loud.
What is not technical drag
It’s just as useful to be clear about what does not belong in your inventory, because lumping these in only muddies the water. They’re real problems, they just need to be solved a different way:
- Lack of training or knowledge in a given service, language, or architecture. That’s a learning and onboarding problem, not drag.
- Changing the scope of an initiative as you execute and learn. That’s just how building software works, not debt.
- Keeping services aligned with your evolving engineering standards (removing CVEs, keeping dependencies current, adapting to platform changes). This is normal “business as usual” work for a team. Note the subtlety, though: doing it is regular maintenance, but not doing it is exactly how technical cruft is born.
Wrapping up
None of this is magic. There’s no tool that makes tech debt disappear, and there’s no amount of refactoring that buys you a permanently clean codebase. What this framework gives you is something quieter but more valuable: a shared language, a visible inventory, and the habit of taking on debt on purpose instead of by accident.
Once “we have too much tech debt” turns into “we have these specific items, these ones have repayment plans, and this one on the critical path is our real bottleneck”, the whole conversation changes. It stops being a complaint and becomes something your team actually steers.
Name it, make it visible, agree on how you’ll pay it back, and delete more than you think you should. Those are the best advices I can give you today.

