Technical Debt is a little tricky to talk about, especially since no engineer really wants to admit that they might owe tech debt. But with many changes happening on tech teams around the world, I think it’s important to get a sense of what tech debt is and what it really means for your engineering team and product development.
In an ideal world, creating new products would go perfectly every time. The engineering team would have just as much time as they need to execute projects, and they would be able to add just as many features as they wanted to. In reality, though, product development has to happen rather differently.
What is Technical Debt?
Technical (tech) debt, or code debt, refers to the consequence of decisions or steps taken to optimise for speed and deliver solutions quickly, as opposed to taking out the time to fully create them.
A company has objectives, as well as customers whom they’re trying to serve. This means that, often, there are deadlines within which the engineering team has to deliver to meet business goals. Usually, though, the engineering team needs more time than is available.
The middle point between creating solutions that work and meeting business objectives is where tech debt lies. Engineers might decide to take a quicker, less intensive shortcut to making that solution available while leaving it open for proper development (refactoring) later on.
Is technical debt necessary?
Think of technical debt like the loan you would take to start a business. You need the money now, but it isn’t otherwise available, so getting a loan means that you can start a business now. But that loan has to be repaid, and everyone knows that repaying loans isn’t particularly fun.
Every engineer and developer would rather not incur tech debt, but real scenarios often dictate otherwise. Most experienced engineers realise that avoiding incurring some form of debt is virtually impossible. That doesn’t make a debt-free product development process any less ideal.
People sometimes go live without certain features because going live as soon as possible is important. If you’re building a user management platform, for example, and you want to go live this week - you have to do a log-in, forgot password, send password via email and send pin features. You might want to add a fingerprint lock feature as well. These are all good features. But you can look at it and decide that you just need your users to be able to log in successfully, and when you’re live, you work on the other features like the fingerprint login, for example.
Conversely, it’s also the way you structure your code. This could include building a single microservice instead of multiple microservices. You know deep down that you should have built multiple microservices, but it will take a while. So you can build one, knowing it might break in the future, but you’d have to fix it before then; if not, it can go bust. There’s usually a point where you’ll know - sometimes, it can be the number of users or activity level. What you built might not be able to handle 2 million users a day. Maybe it can take 100 users a day, and you decide that before you get to 2 million users a day, you’ll fix it.
Building your MVP
Instead of incurring debt, an engineering team might decide to focus on building an MVP. An MVP is a minimum viable product. That is, what makes your product or service usable? Say your company wants to go live today; what can you do to go live? What’s the minimum you need? Instead of writing code incorrectly, you can keep the extra features aside and focus on building an MVP.
With an MVP, you can focus on building something that works without necessarily accruing any debt. It’s arguably better to have calls requesting new features or complaining about side features than writing a primary thing wrong. Instead of owing the debt, just don’t build it. And even if you’re building something you’ll fix later, build it in a modular way. That is what microservices are for. So you can make it better instead of having to edit and fix the components you put in incorrectly.
The debt you don’t want to owe.
When we talk about tech debt, it generally applies to smaller things and features that can be fixed or tweaked without breaking the entire system. There’s a kind of debt that you don’t want to owe. Taking shortcuts to expedite the process only works when you have a foundation or core structure, but the foundation of your code needs to be stable. If you start out with a poor foundation, in the end, the interest on the debt becomes too much. So while you want to borrow to save time, you must be conscious of how big a debt you would owe. No one takes a million-dollar loan just to open a petty shop. If the debt isn’t worth it, then you’re much better off painstakingly building it properly from scratch.
That’s where a good engineering leader comes in. Sometimes it’s the work of the engineer to explain why the team needs to do things painstakingly now to the business leaders. It’s also the work of a good engineering leader to also meet the business objectives. So he has to meet them halfway and know what he can do without for now. Tech debt is generally a rather grey area, but here are some things you should note;
Properly build the core structure.
There are some debts that you shouldn’t owe. Classify it properly and recognise that some things are too big of a debt to leave now. Sometimes the engineering leader or manager sort of ‘goes rogue’. He might need to put in extra hours to meet the goals because he knows that taking shortcuts might lead to serious consequences in the future. Business leaders might suggest a shortcut, but you should identify when the debt is too serious to pay. Take time to build it properly because you’re ultimately responsible for whatever mess occurs due to unnecessarily high debt.
Don’t leave your debt unpaid.
Of course, you’ve convinced yourself that you’ll return to fix it, but the truth is that a lot is happening. While you’re intending to fix it, the business might bring 7 more features that you need to add. So you leave the initial debt and don’t come back to it because there’s something more pressing you need to attend to. If the debt you left isn’t such a big deal and the impact might not be too much - it’s not a problem, and you can return when everything has settled. But if it’s very serious, you might run into trouble down the line that you might not be able to recover from. So don’t leave your debt unpaid. Always fix it.
Do not write code you cannot fix in the future.
The structure of what you’re building needs to be sound, and it needs to be built with the open-and-close principle. It should be open for expansion and closed for modification. You shouldn’t need to shut down the entire service before fixing whatever debt you’ve incurred. Do it in a way that you can fix it without everything going down. Sometimes tech debt is a result of sheer ignorance. Someone without experience might incur unnecessary debt. If you built a solution with 1000 users and architected it a certain way. By the time it got to 20 million users, they might have encountered a problem and learned that you should have started off differently. With that experience, the next time you want to build a solution, you’re going to start off building it the right way. So avoid writing code you can’t fix in the future, and of course, avoid incurring unnecessary debt.
Tech debt vs bad code
Tech debt is a necessary risk you might have to take, but bad code is always a problem. Bad code is a different dynamic altogether because when you get to the point when you want to pay back the “debt” and fix it, it’s always too late. This is because it has this way of continuing to grow until it becomes a behemoth.
Bad code is bad code.
With bad code, refactoring is a very expensive endeavour. Refactoring is a live system, and people must not know you refactored, so everything must work exactly the same way it did after you’re done. Now, if the code was messy in the first place, you probably would not have proper documentation. Most of the time, you’ll end up introducing a bug while trying to fix it. So you might have to rewrite many things, and by that time, the context might have been gone. You see a line, but you’re not sure why this person wrote it, so you take it off, and boom, users can no longer log in because one line of code is missing.
Bad code is a debt you don’t want to owe. Code only grows, and the way code grows is exponential. One day it’s small, and before you know it, it has hundreds of thousands of lines, and you’re wondering what the heck happened. And if you don’t have a system from the beginning, it can get messy really quickly.
So, should you owe tech debt or not?
With tech debt, it’s often case-by-case. You really need to understand the situation and know the risks. Because, in the end, you can’t always want to build the perfect system.
No matter what you think about it, if you want to build the perfect system - a “science project” with very abstract classes - the best pattern and the best design, the world will move on before you’re done. Before you’re done, someone else might bring up something that is barely working and take all the customers. So yes, yours works perfectly, but everybody has moved on. You miss the first-mover advantage. You might have the perfect solution, but there’s a need for balance to meet business goals.
So there are no absolutes. Avoid unnecessary debt, but if you must, just do it in a way that you’ll be able to pay it back. Only owe tech debt that you can repay in the future and that you’d have the time to fix.