Six sources of technical debt (and what to do about each)
Every piece of technical debt traces back to one of six systemic sources. Understanding the source is the prerequisite for choosing the right remediation. The same codebase pattern can come from a rushed deadline (fix: process), a departing expert (fix: documentation), or a premature abstraction (fix: simplification), three completely different responses.
For comparison: rework costs arise from the same sources. See reworkcost.com on unclear-requirements rework for the sibling treatment.
Jump To
Source 01
Rushed Deadlines
The most common source of technical debt across all team sizes. External schedule pressure forces shortcuts that become permanent defaults.
Mechanism
When a deadline is immovable and scope cannot be reduced, engineers make quality trade-offs. Tests are skipped. Abstractions are simplified. Documentation is deferred. The problem is not the trade-off itself, Fowler's Deliberate+Prudent quadrant validates this, but that the cleanup is rarely scheduled with the same discipline as the original feature.
Evidence
DORA's 2024 Accelerate State of DevOps report found that unstable organisational priorities lead to meaningful decreases in productivity and substantial increases in burnout, even in organisations with strong leaders and good internal documentation.
Named example
A feature team shipping an MVP for a board demo in three days skips integration tests, hardcodes a configuration value, and uses a synchronous API call where async is needed. The MVP ships, the board approves. The cleanup tickets are never prioritised because Q2 brings a new set of deadlines.
Remediation
Buffer the schedule (reserve 20% for quality). Renegotiate scope rather than quality. Ship narrower features done properly rather than broader features done poorly. Create the cleanup tickets at the same time as the feature tickets and give them the same sprint priority.
20% sprint allocation lever ->Source 02
Legacy Migrations
Code that was written under earlier norms, patterns, and constraints that is now out of step with the team's current practices and tools.
Mechanism
Legacy code is not bad code, it is code that reflects the knowledge and constraints of when it was written. The debt accumulates when the surrounding context changes (new language versions, new patterns, new team norms) and the existing code does not migrate with it. The interest rate is a function of how often the code is touched.
Evidence
Dropbox has published a detailed account of migrating its desktop client from Python 2 to Python 3, a multi-year effort for a single codebase. Stripe's 2018 Developer Coefficient found developers spend 17.3 of a 41.1-hour week on maintenance, including 13.5 hours on technical debt.
Named example
A company was founded in 2010 using jQuery and server-side rendering. The team has moved to React and REST APIs, but the customer-facing portal still runs the original jQuery stack. Every new feature requires shipping two implementations: one in React for the new interface, one in jQuery for the portal. The portal has 40,000 users who have not migrated.
Remediation
Strangler fig pattern: build the new implementation alongside the old, route traffic incrementally, deprecate the old when traffic is migrated. Architecture Decision Records to prevent the same pattern recurring. Set a sunset date and hold to it.
Strangler fig pattern ->Source 03
Dependency Drift
Transitive dependencies update, APIs change, breaking versions accumulate. The codebase falls behind the ecosystem it depends on.
Mechanism
Open source libraries release breaking changes. Cloud provider APIs deprecate. Language runtimes go end-of-life. Each deferred update increases the upgrade complexity: going from version 3 to version 5 is exponentially harder than 3 to 4 followed by 4 to 5, because each intermediate version introduces different breaking changes.
Evidence
The Log4Shell vulnerability (CVE-2021-44228, CVSS 10.0) exposed the severity: teams whose dependency upgrades were overdue faced a harder emergency patch than teams that were already current.
Named example
A Node.js application running version 14 (end-of-life since April 2023) with 200 npm dependencies. The team has deferred the upgrade because the last time they tried, 23 packages broke. The upgrade project is estimated at 4 engineer-weeks. There are currently 7 known critical CVEs in the dependency tree.
Remediation
Dependabot or Renovate for automated dependency PR creation. Software Bill of Materials (SBOM) for auditability. Quarterly dependency-update sprints, never skipped. Strict policy: no dependency more than one major version behind the current release.
Automated quality gates ->Source 04
Expertise Loss
Senior engineers leave. Tribal knowledge evaporates. The codebase becomes opaque to everyone who remains.
Mechanism
Adam Tornhill's research (CodeScene, 2015-2024) on truck factor shows that most codebases have significant knowledge concentration: a small number of engineers hold almost all the knowledge for high-complexity, high-churn modules. When those engineers leave, the effective interest rate on that code spikes. Every change takes 2-5x longer for the remaining team.
Evidence
Adam Tornhill's CodeScene measures knowledge concentration directly from version-control history: which modules have a single main author, and what happens to them when that author leaves. Knowledge loss shows up as code that nobody remaining has worked on, which is exactly the code teams are most afraid to change.
Named example
The authentication service was written by a senior engineer who left 18 months ago. She is the only person who understood why the session token uses a custom HMAC rather than a standard JWT. The service has 12 open issues, 6 of which are security-related. Nobody wants to touch it because nobody understands it.
Remediation
Architecture Decision Records written at time of decision (not retroactively). Pair programming rotation: all complex modules should have at least two engineers with deep familiarity. Documentation-as-code in the same repo as the service. Quarterly 'archaeology sessions': senior engineers walk junior engineers through historical decisions.
Architecture Decision Records ->Source 05
M&A Integration
Acquired company's stack differs from the acquirer's. Two codebases, two philosophies, one integration deadline.
Mechanism
M&A due diligence rarely includes a rigorous technical debt assessment. The business team agrees the deal; the engineering team inherits a foreign codebase with unknown debt, different conventions, and a hard integration deadline. The resulting systems are often deeply coupled but architecturally incompatible, held together by integration layers that nobody fully understands.
Evidence
KPMG's 2025 Technology M&A Survey of 135 US organisations found that most respondents believe unresolved tech debt can lead to operational disruptions (70%) and cyberattacks (60%), yet fewer than a third deem addressing it pre-deal of utmost importance, so much of it is inherited and dealt with after close.
Named example
A Node.js-native SaaS acquires a competitor whose core product is a Ruby on Rails monolith. The acquirer's platform team has zero Rails expertise. The acquired company's team of eight engineers has signed 12-month retention packages. At month 13, five of the eight leave. The Rails system processes 30% of revenue. Nobody on the acquiring team can modify it safely.
Remediation
Two-track strategy: run the acquired system in parallel while building a strangler replacement. Or: deliberate sunset with a customer migration programme. Either way, write ADRs immediately for all inherited architectural decisions. Never attempt a big-bang integration.
Strangler fig pattern ->Source 06
Premature Abstraction
Engineers abstract for imagined future needs. Creating flexibility nobody uses. The resulting code is harder to understand and modify than a simple direct implementation.
Mechanism
Sandi Metz's rule: 'duplication is far cheaper than the wrong abstraction.' Premature abstraction is the most insidious form of inadvertent debt because it looks like good engineering at the time. The abstract is usually written by the most senior engineer. It gets used everywhere. The imagined use-cases never materialise. The abstraction calcifies and becomes a constraint.
Evidence
John Ousterhout's 'A Philosophy of Software Design' (2018) documents complexity created by over-engineering: 'If a system has many layers of abstraction, the programmer must understand all of them to understand the system.' Ousterhout specifically calls out the 'classitis' anti-pattern: systems with hundreds of tiny classes that are hard to understand in isolation.
Named example
A plugin architecture built for a system that currently has two plugins and will likely never have more than five. The architecture supports an arbitrary number of dynamically loaded plugins with versioned interfaces and dependency injection. It took six weeks to build. Understanding it takes a full day. The two plugins exist. Nobody is building a third.
Remediation
Rule of three: do not abstract until you have three concrete cases. Inline the abstraction if it has fewer real consumers than layers. Ousterhout's principle: prefer deep modules (small interface, lots of functionality) over shallow modules (large interface, little functionality).
Boy-scout rule ->Continue Reading
Sources
- Standish Group. CHAOS Report 2020. Standish Group International.
- DORA (Google Cloud). Accelerate State of DevOps Report 2024.
- KPMG. How AI can help reduce tech debt in M&A (2025 Technology M&A Survey).
- Stripe. The Developer Coefficient. 2018.
- Snyk. State of Open Source Security 2024.
- Tornhill, A. Your Code as a Crime Scene. Pragmatic Bookshelf, 2015.
- Ousterhout, J. A Philosophy of Software Design. Yaknyam Press, 2018.
- Metz, S. Practical Object-Oriented Design in Ruby. Addison-Wesley, 2018.