It's Time to Handle Technical Debt in Your Legacy Application—4 possible scenarios

Cutting corners in web development speeds up your development in the short run. But it eventually catches up to you in the long run, causing a lot of stress and maybe even bigger problems. It can kill morale and could be a reason why developers jump ship from your company. Especially when the business side of things doesn’t care about the code and quality software architecture.

If you feel lost and overwhelmed with technical debt in your legacy application, yet you don’t want to slow down with the development of new features, this article might help you make some decisions about what to do next.

I am dishing up 4 scenarios for handling technical debt we outlined for a potential customer (now our partner) that approached us feeling fed up with their product quality at that time. I will mention some of the problems they had, and our recommended solutions for tackling them while developing new, relevant and interesting features for the users on their web platform.


Let’s take a look at some of the problems we spotted when we looked into their code.

Some of the issues included:

  • Messy code: everything is thrown together in one file: PHP, javascript, HTML—all layers and languages kept together; no templating engine, no patterns like MVC, no separation of concerns
  • Hardcoded configuration variables like: URL’s, database credentials, API credentials etc.
  • Low-quality code: a lot of copy-paste, no good design patterns, no coding style, old code kept in repository
  • Hardcoded libraries (lack of package manager)—hard to upgrade
  • No tests and the codebase was very hard to test—even harder to upgrade
  • Flooding error log with errors caught every second
  • No automation of common processes: releases, database migrations etc.
  • In general: very difficult to maintain, debug, upgrade and keep secure
  • Many security issues including simple attacks like: SQL Injection, XSS etc.

After reading all this you probably find yourself panting with mental exhaustion. So were we, and it was frightening that they weren’t even aware of most of the problems…

When code gives you nightmares you’re left with two options: retreat with your tail between your legs or put yourself to work and start cleaning up the mess.

Let’s not judge but focus on a solution.

We outlined the following scenarios. Some are obviously not serious options, but I am presenting them to show the difference between each approach.

  • Scenario #1 Continuing to develop the old codebase
  • Scenario #2 Continuing to develop the old codebase, but with a focus on refactoring
  • Scenario #3 Rewriting the whole system from scratch
  • Scenario #4 Rewriting and refactoring the system part by part


This is the fastest scenario to start with but it comes with three major issues: it continues to increase the technical debt, it does not allow outdated/insecure dependencies to be updated, and the development slows down in time.

It is a very risky approach for long-term projects. Why? Because the technical debt continues to increase and it is harder to introduce new features over time. Also, possible security bugs in libraries and PHP are a huge disadvantage of this scenario.

Advantages: Ability to introduce features from day one; No refactoring costs
Disadvantages: Technical debt increases, It becomes harder and harder to introduce new features over time; May lead to security flaws; Forces us to stick to the old technology

Scenario #1


Similar to scenario 1. but the focus is kept on refactoring—this makes early development sluggish. However, after a longer period of time, the technical debt is decreased and development does not slow down as much as in the first scenario.

Advantages: Ability to introduce features after a short period; The code gets slowly improved _ Disadvantages: _It takes a longer amount of time to secure the system; Requires to stick to the old technology, unless the code gets rewritten; It requires to us reserve a huge amount of time on refactoring

Scenario #2


This scenario is often suggested but it’s quite tricky because it freezes new features for a long time and when the new version is finally released, it usually comes with new bugs. This scenario is quite bad for an ongoing business because of the feature freeze.

Advantages: Significantly lowers technical debt
Disadvantages: Huge overall cost—all functions need to be written from scratch; Long delay from start to releasing new features

Scenario #3


This scenario allows for both: paying the technical debt, as well as introducing new features. In this case, the new system is built and connected to the old one, without coupling them together. It means that we slowly move from the old system to the new version. It also enables us to introduce new features step by step—it can be spread-out over time, and each feature can be introduced for a selected subset of users first (canary release strategy).

Advantages: Allows to pay back technical debt, Features can be added after a couple of days/weeks; Quick way to introduce SOA architecture; Easy to implement; Makes implementing new features cheaper (after start period)
Disadvantages: It requires us to implement a connection before starting with new features; Need to prepare development environment and other automation tasks for two separate projects

Scenario #4


After we figured out the trade-offs between the different scenarios, we chose the 4th scenario — “Rewriting and refactoring the system part by part”. This is the right approach to use in a situation when there are long-term plans for the system development. It requires a bigger investment at the beginning although still much lower than rewriting the system from the scratch, but the solution pays back over a longer time period. I will describe the process below.

This scenario perfectly matches our development plan that assumes we split the application into a set of smaller microservices — also called SOA architecture. The main advantage of this solution is that it can continually function how it always has with small upgrades or updates happening periodically, compared to a major rollout.


As mentioned before, the chosen scenario requires us to spend more time at the beginning, setting up foundations on which further development may be successfully performed. Thanks to the solid foundation, the initial development is faster and thus cheaper in the long run.


We start with some basic setup work—including automated development environment setup. This will make introducing new developers to the system much easier in the future. Also, important processes like deployment and testing need to be automated. There is no place for errors/bugs/issues to be introduced due the human factor. As many processes as possible should be automated to avoid human error and to speed up the entire process.


The next step is to set up the new version of the system. It has to be connected to the old one—user sessions and data need to be shared between both versions. We also need to design and implement a mechanism that will allow us to switch the versions seamlessly based on the queried feature (URL) or user. This is a very important step, and as mentioned before, this will rise the initial cost, but will significantly lower the cost of the development and maintenance in the long haul.


Before implementing new features we need to conduct testing of critical paths, like signing-in and other basic system features that may be influenced by the ongoing development. This step is done to provide stability to the system when adding new features. Tests will be run after each deployment.


After the 3 steps described above, everything is ready and new features can finally be implemented. Introducing new features will follow a very simple pattern:

  1. Writing a test to ensure that the specific feature is working, especially if the feature is migrating from the old version. If the feature has not yet been implemented—the test describes how it should work
  2. Refactoring the old version in order to decouple important parts
  3. Implementing the feature in the new system, using the decoupled code from point 2
  4. Testing
  5. Canary release (eg. selected clients/users)
  6. Final release
  7. Next feature implementation—back to point 1


There are many factors that you need to analyze before choosing a particular scenario. It’s important to remember that there are many dependencies as well as many reasons why technical debt grows for so many. Just like with financial debt, it brings value but comes with a price. Even if your technological debt is big, there is always a way to get you back on track and make your users happier with a slick application and leave your competition behind. You need to quantify the debt just like one does with financial debt. Having a rough idea of the level of debt will help in choosing the best way to handle it. If you are eager to learn more about tech debt check out our new article "Technical Debt - the silent villain of web development."

Read also: Technical Debt - The Number One Reason Why Software Development Projects Get Derailed
[Polish version]: Dług technologiczny – najczęstszy powód wykolejania się projektów


Ready to make your SaaS Scalable?

Fix most important issues within days from the kick-off


Related posts