There is no such thing as Technical Debt

In a traditional factory, machines are used to build products. Products leave the factory and the machines stay.

In software, every unit of code built is a machine but nothing leaves the factory. Everything that is built stays inside the organization forever.

In a company that creates and uses its own software, every time you build a unit of code, you add a new machine to your factory floor.

But uniquely with software, to build the new machine we can use those that have already been deployed.

The code already deployed was designed and built using the information, resources and requirements available at the time.

When we come to change that code we are updating it to meet the needs of a new requirement that was unknowable when the original code was deployed.

An amount of effort will be required to update what we have to both work with the new requirement and continue to satisfy the original needs.

The code being updated may appear to not support the new requirement very well.

You may wish a different solution had been taken to write the now deployed code so that adding the new requirement is smoother.

But that was impossible.

When the deployed code was written, the new requirement was unknown.

The deployed code was written to satisfy the demands of what was known at the time.

Only later are we looking at the old code in the light of the new requirement wishing we had a different solution.

That is not technical debt.

It's not technical debt but cost of change

You need to make a change but feel that the codebase you have to work with is making the update harder than it should be. If only they had listened; technical debt is getting out of hand.

You write the code based on current requirements, resources, and business priorities.

A month later a second change is required to the same part of the codebase. If only you had known about this change when you did the first one, you would have done something different.

But, that is not how software is produced.

When you implemented the first change the second change was unknowable: it was one month in the future.

And that is the fundamental issue in software engineering.

The changes to be applied today are made to code that was updated in the past in a way that could never have anticipated current requirements.

All change must be applied retroactively.

If this is useful, the free newsletter goes deeper. It is written for people who follow this work closely, and it includes pieces that never appear on the site. Subscribe

An Example

Imagine a website that lets a user store notes in the cloud.

The business would like the first version released as soon as possible to beat the competition. For the business, releasing a solution quickly is the number one priority.

To meet the business requirement, engineering takes the decision to store notes in files, one note per file in a directory whose name is the date and time the note was created. This is not a shortcut; it is a fundamental design decision.

When the user logs in, code just reads through all the directories and files and builds the interface. Adding a new note only requires creating a new directory and adding a file to it. Removing a note is deleting a pre-existing directory and file. Reading a note accesses a file and updating a note updates a file.

This design is used, all the code is written and tested, and the first version is deployed on time. Users love being able to save their notes in the cloud.

Version Two

Users would like to be able to send a note to another user.

The top priority of the business is to once again get this new capability into the hands of the users as quickly as possible. Competition is red hot and this new feature is seen as unique and a driver to thousands of users signing up.

Engineering is at a fork in the road.

Keep using files

One approach is to keep the file-based solution and add support to send notes.

This requirement will require handling errors, concurrency, and atomic update.

Errors must be handled because when a user sends a note to another, the process that handles the send may crash half way through.

Concurrency occurs because two users may send a note to a third at the same time.

If a process fills a disk while writing a note, the note will only have been partially delivered.

Without handling this case, the user will see partial notes, confusing them and the person that sent the note, reducing the quality of the user experience for them both.

In this situation, having atomic update as a fundamental characteristic of data change will reduce the risk of delivery of version two. With an atomic update, the whole note is written. If it cannot be fully written, the partial write is removed. This "all or nothing" approach leads to a better user experience, and the underlying reason preventing the full write can be investigated and resolved.

Use some other persistence mechanism

The engineering requirement to handle errors, concurrency and atomic update have been identified.

A database (relational or NoSQL) would help to address both concurrency and atomic update. Engineering could build a solution where a tried-and-tested solution was available out of the box. This would help to de-risk the delivery of version two and save the organization from having to provide concurrency and atomicity mechanisms using files which are not intended for such use.

The engineering decision

To decide between the two options, engineering estimates the following:

  • Using files requires ten units of work to support shared notes
  • Using a database requires twenty units of work

Twenty units of work are required: 15 to introduce a database and have version one safely use it, and five units of work to build notes sharing for version two.

As moving away from files to a database is estimated to take twice as long, continuing with files is seen as the better solution for the business and its users.

The business sees this as the better solution primarily because version two can be delivered within 10 days. Making this feature available will attract more users, which is more revenue opportunities. Getting it out as fast as possible will distinguish this company's offering from those of the competition.

Releasing this feature is the top priority for the business. And doing it in ten not twenty days is a no-brainer.

From a business perspective, the two solutions are equivalent: they both suppport note send. Engineering knows that the consequences are not equivalent but that issue is not seen as important enough to justify the slower roll-out.

From a business perspective, keeping files is an engineering issue, and engineering will have to deal with it.

The consequences

Version two is delivered. Notes can be shared and the engineers built code to handle errors, manage concurrency and delivery atomic update by using files as the persistence mechanism.

Versions three, four and five add more features. During each design discussion, whether to continue with files or move to a database is raised. Each engineering discussion comes to the same conclusion. Moving away from files would delay the release of the version by too much. The use of files is already there, and the code works. Files remain and three future versions are built that way.

If this is useful, the free newsletter goes deeper. It is written for people who follow this work closely, and it includes pieces that never appear on the site. Subscribe

The engineers' view

As version three is implemented, the time taken to deliver the solution is greater than anticipated. Engineering puts this down to having to write code to use files in ways they are not designed to be used. Engineering is starting to have to fight the decisions made for versions one and two.

When looking back, the file decision is seen as the source of the issue.

"If a database had gone in for version two, we would not be in this mess."

The cost of change

Over time, the cost of change has increased.

From version one to two, the cost of change was minimal. Version two was writing mostly new code for note sharing.

The new features for version three take advantage of the support for error handling, concurreny and atomic update introduced at version two.

Let's assume the cost of change for version three is C.

If a database had been introduced at version two, the cost of change for version three would be less than C. How much less is open to interpretation but it would be less as the database would provide out-of-the-box solutions for concurrency and atomic update.

For version three, if the database did not directly solve the issue, the software engineer using the tried-and-tested database product to build what is required will make smoother progress as the database solution will be more robust than the in-house file-based solution.

This is because the database is a product of another company who make a profit from selling a great product. Such a product should be mature and robust. By contrast, the file-based solution is relatively young and so issues will come to light, especially when we consider a file-system is not intended for error-prone, concurrent, atomic update.

But this is not technical debt.

Why is this?

What is technical debt

In 1992, Ward Cunningham introduced the idea of technical debt:

Shipping first time code is like going into debt. A little debt speeds development so long as it is paid back promptly with a rewrite. Objects make the cost of this transaction tolerable. The danger occurs when the debt is not repaid. Every minute spent on not-quite-right code counts as interest on that debt. Entire engineering organizations can be brought to a stand-still under the debt load of an unconsolidated implementation, object-oriented or otherwise.

This metaphor explains a very specific situation that is different to the one described in this article.

Cunningham is describing the situation where engineering teams sometimes ship code they already know is "not‑quite‑right" in order to move faster, with the intention of refactoring it later.

In this metaphor, what is "not-quite-right" is known. The work to remediate it and "make it right" is known, and this is a quantifiable "debt". If the remediation is not performed, the "debt" is not repaid and the extra effort required for engineers to work with "not-quite-right" code is the interest on that debt.

However, in this case, using files was not a shortcut. It was not a small decision that could be easily unwound with a refactor.

Even though using files did not feel "quite right", engineering did not know what amount of work would be required to "make it right". Without knowing this, there is no quantifiable debt and the interest payable on that decision cannot be calculated.

If we view engineering's situation through the technical debt metaphor, the use of files is the debt which was not repaid as files were kept. The greater cost (C) discussed above is seen as the interest on that debt.

However, this is an engineering view comparing what happened with what they feel should have happened. And what should have happened is understood only later once the consequences of the earlier decision are seen through a lens of a new requirement that makes it feel as if extra work is required.

What should have happened is purely an engineering view. What did happen was a business decision.

How did we get here?

Files were chosen for version one as that was seen as a quick solution to market.

Files were kept for version two as the time required to replace them was considered too high.

Technical debt is seen to accumulate from a series of technical decisions.

However, this will always occur.

Decisions being made today cannot take into account unknowable future requirements.

We should have known

Using files was an engineering decision to speed the delivery of version one.

Speed to market was seen as the top business priority and engineering supported that by using a solution it saw as quick, in the short term.

A prompt rewrite did not occur as that was seen as introducing too much delay for version two. This shows the decision to use files was not a shortcut.

In hindsight for engineering, using files seems to be a solution that will only create additional work.

However, that observation was not factored into the decision-making at the start. Speed to market was the top business priority and the consequences of the technical decision were not taken into account.

However, calling the consequence of this decision technical debt implies that:

  • engineering should have designed version one for unknown future needs to reduce future "debt"
  • engineering should have used a database even though files met the requirements at the time

But none of that is true.

If this is useful, the free newsletter goes deeper. It is written for people who follow this work closely, and it includes pieces that never appear on the site. Subscribe

It might be said that choosing files was a bad decision as future note-features would have to live with that decision, and there is some truth to this. However, when version one was built:

  • the future was unknowable
  • using files for error-prone, concurrent and atomic updates was not a requirement

More notes-features beyond version one is likely but not guaranteed.

As this situation is likely, it does make the need to move to another technology more pressing. A decision to use files could have been made on the agreement that version 1.5 would move files to another solution.

Once version one is delivered, the appetitie for the work at 1.5 might dissipate. However, this is the art of the job — keeping the goal in mind and the focus to achieve it.

As the future is unknowable, a decision today that would keep change cost at zero for some future requirement is not possible.

Therefore, no part of the future friction could have been avoided. Therefore, no debt exists.

The file-based solution is only sub-optimal when viewed through a lens of future requirements. The decisions at the time, made sense at the time. Yes, those decisions have consequences, and in hindsight, they appear to add cost. But a cost would also have to be paid to remediate the earlier decision which was not taken due to perceived extra delay.

The cost that is seen when building version three is simply the cost of adapting what you have previously built to support new requirements.

This is the art of engineering. How to take a pre-existing solution and adapt it to support new requirements while at the same time also implement all the old behaviour. Over time, adding new features to pre-existing features is more prone to failure.

This is because you increase the number of constraints the code must satisfy simultaneously. The code now has more states to handle. And this can grow the failure risk.

Failure risk grows because the system is accumulating history. Every change adds assumptions, behaviour, constraints, interactions, exceptions, obscure cases, and historical structure. This accumulation is the unavoidable cost of retroactive change.

When supporting new requirements, there is a gap between what you previously built and what you now require. But there always will be. The future is unknowable.

Could that gap have been smaller? No. It is not possible because when the decision was made, the future requirement that may have benefitted from a different approach was not known.

This leads to the feeling of "we should have known". But why? None of what subsequently happened had to. Possible, yes, Likely, yes. But not guaranteed.

Conclusion

What we have is asset evolution, not debt.

What you have is not technical debt but technical regret.

Historical note

Ward Cunningham introduced the term technical debt in 1992 when describing the WyCash financial system.

He coined the metaphor to help non‑technical stakeholders understand why refactoring was necessary and why postponing it created predictable future cost.

His metaphor explained a very specific situation: engineering teams sometimes ship code they already know is "not‑quite‑right" in order to move faster, with the intention of refactoring it later.

In that context, the debt is real because the team is consciously deferring work they understand and so the future cost is predictable.

Cunningham was not describing the friction that arises when software must evolve to meet requirements that were unknowable at the time of the original design.

His metaphor assumes known shortcuts, known consequences, and deliberate deferral. These are conditions that do not exist in most modern change scenarios.

What is often labelled "technical debt" today is therefore better understood as the normal cost of retroactive change rather than the kind of intentional borrowing Cunningham originally described.

Read next: Why Early Architectural Decisions Restrict Your Future Options
A look at how early choices can shape and constrain every future change.

If this was useful, you can get more pieces like it in the Phroneses newsletter.

Subscribe →

Table of Contents

Further Reading

\