“We have a lot of technical debt” is something every engineering team says. It tells me nothing. Every codebase has technical debt. The question isn’t whether you have it — it’s whether you have the wrong kind or too much of it for your current stage.
Some technical debt is rational. You shipped a feature with hardcoded values because you needed to close a deal. You chose a simpler architecture because you had three engineers and didn’t need the complexity of a distributed system. You skipped writing comprehensive tests because speed-to-market mattered more than long-term maintainability at that moment. That’s not bad engineering — that’s good judgment about trade-offs.
The problem is when rational debt compounds into irrational drag.
The Warning Signs
Your engineers are spending more time fighting the system than building on it. Track this informally by asking in 1-on-1s: “What percentage of your time last week went to working around existing problems versus building new things?” If the answer is consistently above 30%, you have a debt problem. Above 50%, you have an emergency.
New engineers take weeks to become productive. If your onboarding time-to-first-productive-commit is measured in weeks rather than days, your codebase is telling you something. Excessive setup complexity, undocumented dependencies, fragile local development environments, and code that’s impossible to understand without tribal knowledge are all debt symptoms that directly impact your ability to scale the team.
Your team avoids certain parts of the codebase. When engineers say “don’t touch the billing module” or “we work around the payment service,” those forbidden zones are debt that’s actively constraining your product development. Features that should take a week take a month because the team routes around the dangerous areas.
Your change failure rate is climbing. If more than 15% of your deployments cause production issues, your testing, code quality, or deployment processes have degraded to the point where shipping is risky. This is debt with a direct revenue impact — every failed deployment means downtime, customer impact, and engineering hours spent on firefighting instead of building.
You can’t explain the architecture. If no one on the team can draw the system architecture on a whiteboard and explain how data flows through it, you’ve accumulated architectural debt that makes every decision harder. Engineers are making local optimizations because nobody has a picture of the global system.
The Three-Bucket Framework
Not all debt is created equal, and you shouldn’t treat it that way. I categorize technical debt into three buckets, each with a different funding model.
Bucket 1: Safety debt. This is debt that could take the business down. Single points of failure with no redundancy, missing backups, known security vulnerabilities, unpatched dependencies with CVEs, compliance gaps. Fund this immediately and unconditionally. This isn’t a trade-off — it’s risk management.
Bucket 2: Speed debt. This is debt that’s slowing the team down measurably. Slow CI/CD pipelines, manual deployment steps, flaky tests that require manual re-runs, missing monitoring that means you discover problems from customer complaints instead of alerts. Fund this based on ROI: if fixing the CI pipeline saves 8 engineer-hours per week, the investment pays back in weeks.
Bucket 3: Quality debt. This is code that works but is messy. Inconsistent naming conventions, duplicated logic, suboptimal data models, missing abstractions. This debt is real, but it’s not urgent. Address it opportunistically — when you’re already working in the area, clean it up. Don’t launch dedicated “quality sprints” unless quality debt has graduated to speed debt.
The 50/50 Guideline
As a general rule, I recommend that growing engineering teams allocate roughly 50% of their capacity to new features and 50% to quality work — which includes debt remediation, testing, infrastructure, monitoring, and developer experience improvements. The exact ratio varies by week and quarter, but your trailing 90-day average should be somewhere near this balance.
Companies that spend 90% on features and 10% on quality feel fast for two or three quarters. Then they hit a wall: deploys become scary, outages become frequent, and engineers start leaving because the codebase is miserable to work in. The recovery takes longer than the prevention would have.
When to Accept Debt Deliberately
Debt isn’t always bad. Here are situations where taking on debt is the right call:
- You’re pre-product-market-fit and validating a hypothesis. Speed matters more than code quality when the code might be thrown away entirely.
- You’re closing a make-or-break deal that requires a feature in two weeks, not four. Ship it, then schedule the cleanup.
- You’re migrating between systems and the old system won’t exist in six months. Don’t refactor something you’re about to replace.
The key is making these decisions explicitly and tracking the debt you’ve taken on. Rational debt becomes irrational when nobody remembers it was supposed to be temporary.
Related: Tech Debt Translation: Making Your CFO Care | Engineering Metrics That Actually Matter | When to Replatform
