An Introduction to Software Quality (Part 2): What Is Technical Debt?

An Introduction to Software Quality (Part 2): What Is Technical Debt?

Technical debt, a term coined by Ward Cunningham in the 1990s, refers to the trade-off between short-term gains and long-term consequences in the development process. In this article, we will explore what technical debt is, why it happens, why you should care about it, and discover some new tools to help you discover the technical debt in your Rails applications opens a new window .

What is technical debt?

Technical debt refers to the compromise made by developers when choosing a quick and easy solution over a more time-consuming but sustainable alternative. Just as financial debt accumulates interest over time, technical debt accrues potential issues if not repaid. Technical debt can happen in many places during the software development life cycle, including during the design phase, at the code level, in test coverage, or in documentation.

Technical debt can have different meanings depending on who you ask. Stop Saying “Technical Debt” opens a new window is a good overview of all the possible meanings when someone mentions “technical debt”.

Just as the definition of technical debt changes depending on who you ask, the same goes for how to categorize technical debt.

Martin Fowler, a well-known software developer and author, has categorized technical debt into four main types, depending on the motives of the team accruing the debt. These four main types together are called the Technical Debt Quadrant opens a new window .

In 2023, several Google researchers published a paper based on five years of research at Google documenting the tech debt across different teams at the company. The researchers surveyed several subject matter experts to determine the main components of technical debt. They were able to narrow down 10 categories of technical debt. You can read more about these categories, and how Google used this research to manage their technical debt, in Defining, Measuring, and Managing Technical Debt at Google opens a new window

It is important to recognize that technical debt is an inevitable part of the software development life cycle. Tight deadlines, resource constraints, and evolving project requirements often force developers to make trade-offs.

The Difference Between Tech Debt and “Bad Code”

It is also important to recognize that technical debt is not “bad code”, a term technical debt often is confused with. Technical debt refers to the consequences of prioritizing speedy delivery over perfect code. It’s often deliberate, with the understanding that the code will need to be improved later. Technical debt can be managed and strategically used to achieve short-term goals while planning for future refactoring.

Bad code, on the other hand, typically results from poor programming practices such as lack of understanding, inadequate design, or insufficient testing. It might not be intentional and can lead to increased maintenance costs, bugs, and difficulties in extending or scaling the application. Here are some examples of what might often be considered technical debt but are actually just bad coding practices:

  • Spaghetti Code: Unstructured code that is hard to read and maintain, usually lacking in modular design.

  • Magic Numbers: Hard-coded values with no explanation or constant definition, which can be confusing and error-prone.

  • Copy-Paste Programming: Repeated blocks of similar code that could be replaced with functions or loops, leading to bloat and maintenance headaches.

  • Lack of Documentation: Missing explanations for why code is written a certain way, which can make it difficult for others to understand or modify the code.

  • Ignoring Errors: Failing to handle possible error conditions properly, potentially leading to crashes or incorrect behavior under certain conditions.

In practice, the line between technical debt and bad code can blur, as poor coding practices can lead to technical debt if not addressed. However, recognizing the difference is crucial for effective code management and maintenance strategies.

Why should you care about technical debt?

Caring about technical debt is crucial for several reasons:

  • Cost: Technical debt accumulates when shortcuts or suboptimal solutions are implemented to meet deadlines quickly. Over time, the cost of maintaining and fixing issues related to these shortcuts can escalate, making it more expensive in the long run.

  • Productivity: As technical debt increases, developers spend more time dealing with issues, bug fixes, and workarounds. This can hinder productivity and slow down the development process, making it harder to deliver new features or improvements.

  • Quality: Technical debt often leads to code that is less maintainable, readable, and scalable. This can result in lower overall software quality, making it prone to errors, difficult to enhance, and challenging for new developers to understand.

  • Innovation: Accumulated technical debt can stifle innovation as resources are tied up in resolving existing issues rather than exploring new ideas or implementing improvements. This can hinder a company’s ability to stay competitive in the rapidly evolving tech landscape.

  • Customer Satisfaction: Poorly maintained software with a high level of technical debt can lead to a subpar user experience. This can result in customer dissatisfaction, reduced user engagement, and even loss of customers to competitors with more stable and feature-rich solutions.

  • Long-Term Viability: Neglecting technical debt can jeopardize the long-term viability of a software project or product. As the debt accrues, it becomes increasingly difficult to maintain and enhance the system, potentially leading to a point where a complete rewrite is necessary.

Measuring Technical Debt

Now that we know about what technical debt is and its’ causes, let’s talk about how we can measure it. Measuring technical debt is inherently a complex task - to be able to measure it, it first has to be defined. We usually recommend teams to have a conversation about what they perceive as technical debt. Once they arrive at a baseline definition of technical debt that makes sense to them, they can identify metrics and start tracking them.

Here are some of the tools that we have found useful at to measure technical debt:

  • Libyear or libyear-bundler opens a new window : This tool revolves around the concept of libyears opens a new window . A libyear measures “dependency drift”, or the measurement between your dependencies’ installed versions and the latest stable versions, in years. Libyear-bundler compares the outdatedness of dependencies using time as a metric.

Here’s what a sample libyear-bundler report looks like. It shows a list of the current versions and the most recent versions of a gem.

% libyear-bundler Gemfile
addressable      2.8.5      2023-08-03         2.8.6      2023-12-09       0.4
bootsnap         1.16.0     2023-01-25         1.18.3     2024-01-31       1.0
capybara         3.36.0     2021-10-25         3.40.0     2024-01-27       2.3
childprocess     4.1.0      2021-06-09         5.0.0      2024-01-07       2.6
concurrent-ruby  1.2.2      2023-02-24         1.2.3      2024-01-16       0.9
date             3.3.3      2022-12-19         3.3.4      2023-11-07       0.9
ffi              1.15.5     2022-01-10         1.16.3     2023-10-04       1.7
globalid         1.1.0      2023-01-25         1.2.1      2023-09-05       0.6
i18n             1.14.1     2023-06-04         1.14.4     2024-03-06       0.8
jbuilder         2.11.5     2021-12-21         2.12.0     2024-04-29       2.4
listen           3.8.0      2023-01-09         3.9.0      2024-02-24       1.1
loofah           2.21.3     2023-05-15         2.22.0     2023-11-13       0.5
marcel           1.0.2      2021-09-20         1.0.4      2024-03-01       2.4
method_source    1.0.0      2020-03-19         1.1.0      2024-04-15       4.1
mini_portile2    2.8.4      2023-07-18         2.8.6      2024-04-14       0.7
minitest         5.19.0     2023-07-26         5.22.3     2024-03-13       0.6
net-imap         0.3.7      2023-07-26         0.4.10     2024-02-04       0.5
net-protocol     0.2.1      2022-12-08         0.2.2      2023-11-07       0.9
net-smtp         0.3.3      2022-10-29         0.5.0      2024-03-26       1.4
nio4r            2.5.9      2023-04-02         2.7.1      2024-03-20       1.0
nokogiri         1.13.10    2022-12-07         1.16.4     2024-04-10       1.3
public_suffix    5.0.3      2023-07-11         5.0.5      2024-04-02       0.7
puma             5.6.7      1980-01-01         6.4.2      2024-01-08      44.0
racc             1.7.1      2023-06-14         1.7.3      2023-11-04       0.4
rack             2.2.8      2023-07-31         3.0.10     2024-03-20       0.6
rack-proxy       0.7.6      2023-01-17         0.7.7      2023-09-01       0.6
rake             13.0.6     2021-07-09         13.2.1     2024-04-05       2.7
regexp_parser    2.8.1      2023-06-10         2.9.0      2024-01-07       0.6
spring           3.1.1      2021-11-25         4.2.1      2024-04-22       2.4
sprockets        4.2.0      2022-12-20         4.2.1      2023-09-05       0.7
sqlite3          1.6.4      2023-08-26         2.0.1      2024-04-20       0.7
thor             1.2.2      2023-05-11         1.3.1      2024-02-26       0.8
tilt             2.2.0      2023-06-05         2.3.0      2023-09-14       0.3
timeout          0.4.0      2023-06-23         0.4.1      2023-11-07       0.4
web-console      4.2.0      2021-11-17         4.2.1      2023-09-05       1.8
zeitwerk         2.6.11     2023-08-02         2.6.13     2024-02-06       0.5
ruby             2.6.0                         3.3.1                       0.0
System is 96.7 libyears behind
  • bundle_report outdated: This is a tool in our next_rails gem opens a new window that we created to help developers extract more useful information about their gem dependencies. This tool lists all the outdated gems and measures what percentage of your gems are out of date.

Together, libyear-bundler and bundle_report outdated can help you determine your application’s dependency freshness opens a new window . This metric can help you understand if and how outdated your dependencies are.

Below is a sample homepage in Ruby Critic. It shows the overall score, a churn vs complexity graph for the project, and a list of the files, churns, and smells of a project:

This is a sample homepage in Ruby Critic, showing the overall score, a churn vs complexity graph for the project, and a list of the files, churns, and smells of a project.

  • SimpleCov opens a new window : This tool shows the percentage of total test coverage of an application and the percentage of test coverage for individual files. This metric will help you determine how much of your code is exercised by your test suite.

Below is a sample output for SimpleCov. It shows what percentage of the files that are covered by tests:

A sample output for SimpleCov, which shows what percentage of the files that are covered by tests.

Below is a sample output for Skunk. The files are organized by SkunkScore, from highest to lowest:

This is a sample output for Skunk. The files are organized by SkunkScore, from highest to lowest.


Technical debt is an inevitable reality in software development. Understanding its various types and causes is crucial. By understanding why technical debt happens and knowing what tools you can use to measure it in your codebase, you can begin to develop a strategy to effectively manage and reduce it in your project.

Interested in paying off 1% of your tech debt every month? Let’s talk! opens a new window

Get the book