I was brought in to help a team that had spent the better part of a year decomposing their monolith into microservices. Twelve services, proper domain boundaries, Kubernetes, the works. The architecture diagrams looked impressive. The tech lead had presented their migration story at a conference.
They'd eliminated the "big ball of mud" and could now scale services independently, even though they'd never actually needed to scale anything independently.
When I started looking at their delivery metrics, something didn't add up. Features were taking longer to ship than before the migration. A simple change to how notifications were triggered required touching four services, coordinating three teams, and sequencing four deployments. What used to be a one-day change in the monolith had become a two-week coordination exercise.
The services had been split by technical layer, not business domain. So a single user-facing feature would cut across the API gateway, the user service, the notification service, and the event processor. Each service had its own repo, its own deploy pipeline, its own on-call rotation. Every feature became a cross-team project.
When I asked why they'd drawn the boundaries this way, the room went quiet. The original architect had left eight months ago. The decisions made sense at the time, apparently. Now nobody could explain the rationale, and the service boundaries were considered too risky to redraw. Changing them would mean migrating data, rewriting integrations, updating every client. Easier to just work around it.
So they did. Engineers learned which features to avoid proposing. Product managers learned to keep scope small. Everyone learned to pad estimates for anything that touched "the notification flow." The friction had become invisible because they'd stopped trying to push through it.
Meanwhile, one corner of the old monolith was still running. The billing module. It was ugly, poorly documented, with code from 2017 that made engineers wince. It had been next on the decomposition list for two years. But whenever a billing feature came up, it shipped fast. One repo, one deploy, one team. The code was embarrassing. The velocity was excellent.
The microservices were clean. The monolith was ugly. And the monolith was where work actually got done.

Debt is the wrong metaphor
The word "debt" implies something you owe. Something accumulating interest. Something you must eventually pay back, whether you like it or not. This framing is why teams treat technical debt like inventory. Catalog it. Score it. Chip away at the backlog. Prioritise by severity.
But that's not how code works.
Code sitting untouched costs nothing. It doesn't accumulate. It doesn't compound. It just sits there. That monolith the team was so desperate to decompose? If nobody was modifying it, if no features required touching it, if it ran stable in production, it was costing exactly zero. The "interest" wasn't accumulating. There was no interest.
The useful metaphor is friction.
Friction only costs when something moves through it. A rough surface causes no drag if nothing slides across it. The moment you need to push something through, that's when you feel the resistance. That's when it matters.
Technical friction is the slowdown that problematic code creates when people work with it. Extended cycle times. Routing around systems. Coordination overhead. Extra debugging. Fear of touching certain files. The key distinction: friction only exists when something moves through it. If nobody's touching that code, if no feature requires modifying that system, the friction isn't costing you anything.
This isn't semantics. The metaphor you use shapes how you think and what you do about it.
What the debt framing gets wrong
When you think "debt," you inventory. You ask "what's our worst code?" You survey engineers about what bothers them. You build a prioritised list based on how ugly, outdated, or non-compliant the code is.
The billing module makes the list. Obviously. It's a mess. Comments from 2017. No tests. Variable names that violate every style guide. It "should" be refactored.
The microservices architecture doesn't make the list. It's clean. It follows best practices. It has proper separation of concerns. The diagrams look professional. Engineers are proud of it.
But the billing module wasn't slowing anyone down. Features touching billing shipped fast. The code was ugly but navigable.
The microservices were adding weeks to every cross-cutting feature. Every engineer who touched the notification flow was fighting coordination overhead. Every product manager had learned to avoid certain types of features entirely. The compound impact was massive.
The debt framing pointed at the wrong target because it measured appearance, not cost. It catalogued what looked bad rather than what was causing problems.
This is the trap of inventory thinking. You end up with a beautiful spreadsheet of "debt items" scored by severity, voted on by engineers, maybe even linked to Jira tickets. And the items on that list have almost no correlation with what's actually slowing you down.
When friction isn't the right frame
Not everything fits the friction model. Some things aren't about speed or efficiency at all.
Security vulnerabilities don't cause friction until they cause a breach. A SQL injection in a rarely-used admin panel isn't slowing anyone down. But it could end your company. This isn't friction. It's risk. Different category, different analysis, different urgency.
Compliance gaps work the same way. Your GDPR implementation might not slow down any feature work. But if you're violating regulations, you face legal exposure that has nothing to do with engineering velocity.
Systems approaching breaking points need attention before they break. A database that's at 80% capacity isn't causing friction today. But if your growth trajectory means you'll hit the wall in six months, waiting until it's friction is too late.
Pretending the friction model covers everything undermines it. It's a tool for thinking about engineering velocity and priorities, not a universal framework for all technical decisions. Security, compliance, and capacity planning have their own logic.
But don't let exceptions swallow the rule. Most of what teams label "tech debt" isn't security, compliance, or capacity. It's code they don't like looking at. For that, friction is exactly the right lens.
The question that changes everything
Replace "what's our tech debt?" with "what's actually slowing us down?"
This question is harder to answer. It requires looking at real work, not code aesthetics. It forces you to examine what happened last quarter, not what might happen in theory. It surfaces friction that was never labelled debt.
If the team I worked with had asked this question, the coordination overhead would have surfaced immediately. Every feature touching notifications was slipping. But nobody was asking "what's slowing us down?" They were asking "what's ugly?" The friction was obvious once you looked at the right thing.
The question reveals patterns everyone accepted as normal:
What normalised friction sounds like
"That's just how the notification flow works."
"Yeah, any cross-service feature takes three sprints. It always has."
"We try to avoid changes that touch multiple services."
These aren't facts of life. They're friction. But teams normalise them because they've always been there.
Fresh eyes find friction faster. New engineers often see it in their first month. But they get socialised into silence by teams that have stopped questioning inherited patterns. When I asked "why does every notification change require four deployments?" as an outsider, I had standing to push. The team had been routing around the friction for so long they'd stopped seeing it.
When you've lived with friction long enough, it becomes invisible. You route around it instinctively. You build workarounds without noticing. You scope features differently to avoid the pain. This avoidance behaviour is the most expensive friction of all, because you never see the features that got de-scoped or the architectural workarounds that became permanent.
Two questions worth asking
When engineers propose rewrites or cleanups, ask:
What gets faster?
What gets unblocked?
If the answer is "the code will be cleaner," that's not friction. Cleaner code is nice. It's not a business priority.
If the answer is "deploys take 40 minutes and this would cut it to 5," that's real. You can calculate the impact.
If the answer is "every feature that touches payments slips two weeks," that's real. You can verify the claim against last quarter's work.
Architectural purity is a preference. Friction is a cost. Learn to tell the difference.
Stop inventorying debt
If you're about to start a "tech debt" initiative, stop. Don't catalog. Don't vote on severity. Don't build a backlog. Instead, look at your last quarter of work. What took longer than it should have? What slipped? Where did engineers get stuck? What features got de-scoped because they'd require touching certain systems?
That's your friction. That's what's costing you. Everything else is aesthetic preference dressed up as engineering priority.
The billing module is still running. It was never the problem.