Berlin

November 4 & 5, 2024

New York

September 4 & 5, 2024

How to plan for and mitigate different types of tech debt

Whether it's planned or unplanned tech debt, there are several ways you can mitigate big hiccups with the right processes in place.
March 05, 2024

You have 1 article left to read this month before you need to register a free LeadDev.com account.

The term technical debt encompasses a broad array of issues, such as planned debt, naturally depreciating code, and architectural changes.

All software teams will have to deal with technical debt eventually. Often this happens because we have already let things get pretty bad, and now we have to dig ourselves out. There is a variety of technical debt that can accumulate within a system, and no single solution can be applied universally, but by identifying the source of certain types of debt and proactively planning to address them, we can stop interest damage from accruing. 

Planned debt: Trading quality for speed

The first, purest sense of the debt metaphor is accepting a future burden in exchange for immediate benefit. This trade is often sacrificing quality for speed, by accepting confusing, inconsistent, or poorly structured code in order to ship a product or feature sooner. Customers and the business get value, but we have knowingly made it harder for ourselves to maintain our current pace and level of product quality.

Debt like this is often a deliberate choice made by a team, and since it is a choice we make knowingly, we have an opportunity to set the “repayment terms.” Like all debt, this carries interest – in the case of technical debt, everything we build on top of our original code has to work around whatever sacrifices we made. The fastest way to minimize the debt’s impact is to eliminate it as soon as we can.

After shipping a new feature or experiment, many teams will have a period of observation and measurement, with relatively few additional product changes. When shipping the feature does involve taking on tech debt, this measurement period can be a good time to tackle any refactors. While planning the release, the team can set aside this time immediately after the release for this clean-up.

This approach can be called deferred product work. It’s work that would have happened to build this product or feature, were it not for a decision to take on debt. The most effective time to plan to pay down this debt is the same time you decide to take it on, and the sooner you can pay it down, the less it will accrue interest, as less code will be built on top of this debt, meaning fewer workarounds and less extra work.

Maintaining the aging code that powers a product

Software ages. Languages, operating systems, runtimes, frameworks, libraries, and remote APIs all release new versions, sometimes with urgent security fixes. Even design best practices in the code will change over time as the business evolves – affecting the way the software models the business – and as teams and engineers grow and mature. Teams usually refer to this kind of aging process as tech debt, because it has similar outcomes. A better metaphor, however, might be technical depreciation.

This distinction is important, because debt and depreciation come from different sources. Debt, in the sense of borrowing against the future, comes from making (hopefully deliberate) trade-offs. Depreciation arises from the way our raw materials degrade with age. These distinct sources mean that effective planning for debt will not work for depreciation, and vice versa.

The nice thing about technical depreciation, from a planning perspective, is that it happens at a fairly consistent rate over time. The specifics may vary, for example, some projects like Ruby, Django, and Go forewarn devs with planned release schedules. Most smaller ones do not. But, on average, dependency updates require a predictable amount of effort. A medium-to-large JavaScript application, for example, probably has 6-8 dependency updates in a typical week.

Many engineering teams follow Marty Cagan’s rule of thumb of setting aside 20% of their time for sustaining engineering work, and this approach is well-suited to the predictability of technical depreciation. However this schedule is implemented, the actual tasks still require prioritization by engineers. For example, refactoring old code to use a new pattern or library may take a backseat in contrast to keeping dependencies up-to-date, if that old code is well-encapsulated and not very contagious. InfoSec teams may set targets or policies for updating vulnerable libraries, which will inform the priority of those updates.

How to factor in and deal with debt accrued through architectural changes

Another significant source of what we call tech debt is a change from outside the system itself. No matter how accurate your initial model of the system is, at some point, some core requirement will change. One magazine brand I worked on had a unified visual identity for years until one day, the business decided they wanted to start selling visually distinct sub-brands. Systems optimized for selling one particular product suddenly needed to grow to accommodate new product lines. 

Changes to fundamental assumptions or requirements lead to debt when they are left unaddressed. The system architecture reflects a previous version of the business, with the new components loosely attached, instead of fully integrated. As more code is written on top of this shaky foundation, more workarounds will be required, increasing the incidental complexity of the system. Increased incidental complexity leads to higher cognitive load and lower velocity.

Other things to look out for are new regulations or security processes, like the GDPR or FedRAMP, which can also impose new or changed requirements. It can even come from a shift in consumer expectations, like a competitor releasing a significantly faster product.

Strictly speaking, this kind of change isn’t debt. The system architecture needs to change because the business entities that it models have changed. Decisions that were prudent in the past, with the old requirements, are not well-suited to the new needs. Changing the architecture to fulfill the new requirements is part of the cost of adopting those requirements. That means that, ideally, this kind of change should be planned proactively. It may not be feasible to make all the desired changes immediately, but we can develop a roadmap to run alongside continued product investments.

Planning for the unknown

As Chelsea Troy and others point out, the term tech debt encompasses many different ideas. Determining how a particular type of tech debt has arisen – a deliberate choice, a consistent process, an external change, or others – is instrumental in pointing us to the most effective approach to remedying it.