How to balance technical debt
I’ve been a programmer for most of my life, between working for enterprise companies, and startups, and then running my own software development company, so I have seen the impacts of technical debt from all angles.
Avoiding tech debt isn’t as simple as saying “don’t do it”. I would say every single software project has a technical debt of some kind (and not all are accidental).
I think the best you can do is understand and be aware of it.
What is technical debt? 💸
Technical debt is the future cost you take on by doing things quickly, and then having to pay for it later on.
Some examples include.
- Poor code quality
- Rushed architecture decisions
- Using low-quality or unmaintained libraries
And like financial debt, tech debt will (most likely) need to be paid back.
What’s the problem with technical debt? 😬
“You cannot spend your way out of recession or borrow your way out of debt”
– Daniel Hannan, Writer and politician
Let’s say we are building a new web application for a client, allowing musicians from around the world to jam in real-time together.
We go through a scoping phase, and a UI design phase and the client gives the “all clear” to start building.
Awesome, I’m excited!
But when designing and building software, hundreds of decisions need to get made.
- What backend framework should we use?
- What database?
- What frontend tech?
- How should we stream the audio? WebSockets, WebRTC, HTTP Live Streaming?
Each of these decisions takes you down a road. And each road has its own little twists and turns.
- Ok, let’s go with React, good choice
- Should we use a state management library like Redux?
- Do we leverage a UI library?
- Which one? Material UI? Ant Design? Semantic?
- Should we automate front-end testing?
- Using Jest? Mocha? Cypress?
These decisions themselves aren’t technical debt, but they do have the potential to cause or increase technical debt.
For the real-time music jamming web app, you decide to use a specific React UI library you have used before, that previously made things run a lot faster. This project has a really specific UI design and is quite groundbreaking in fact.
As the project gets underway you realise that whilst simple UI components are a lot faster to create, more complex UI actually takes longer, as you have to customise the library to match the approved design.
Building on top of the existing UI library still saved time - just not as much as you had hoped.
This decision doesn’t just affect this specific delivery, it will affect every piece of UI work on this project in the future too. Whether it’s you working on it, or someone else.
This is where technical debt slips in.
A decision to get today's work done faster can often affect tomorrow's work.
Is all tech debt bad? 👎
“It’s not that paying down technical debt will make your startup successful. It’s that successful startups have the luxury of paying down technical debt”
– Naval Ravikant, Entrepreneur and investor
Importantly there are 2 types of technical debt.
- Accidental tech debt - where you make a decision you don’t realise will hold the system back in the future
- Deliberate tech debt - where you make a decision you know may hold the system back in the future, but right now it is the best decision
Avoiding accidental tech debt comes with experience, but there are strategies you can employ to mitigate the chances of it occurring.
- Measure twice, cut once (spend more time planning and researching, before jumping into coding)
- Research all libraries and frameworks deeply before use
- Employ best practice code standards such as linting, peer review, and automated testing
- Refactor fast, as soon as you notice any sign of a possible technical debt
- Book in dedicated time for maintenance and refactoring
Deliberate tech debt is a whole other thing.
In my previous example about the collaborative music jamming app, I would say that the decision about saving some time by using the UI library comes down to the delicate balance of what's most important.
If time is most important, then go with whatever gets the first version of the project done as fast as possible - even if that means future work may be a bit slower.
If cost is the highest priority, then look for the cheapest way to get done.
If it’s quality, you need to build in the best way you can (and perhaps that involves having complete control over the UI and not building on a framework that might restrict you in the future.
It’s not all on your shoulders 🤝
“The risk of a wrong decision is preferable to the terror of indecision”
– Maimonides, Philosopher
Your client doesn’t know about databases, WebSockets, or React UI libraries - that’s your department.
But your client does know what's most important to them and their project, so the decision of time vs cost vs quality is really a decision for them, not you.
In a project scoping workshop or a discovery session I specifically ask the client to pick what are the main priorities of the project, and the tradeoffs they are willing to live with.
They can pick two of the following (but not all three).
I explain why all three aren’t realistic (at least for most budgets), and clients understand this once you explain that software development isn’t just a science, it’s also an art.
Based on the two priorities they choose, you can then make the smartest decision you can in regards to balancing technical debt, and balancing what the client sees as the highest priorities.
For example, a startup might be looking to get an MVP built, in which case they would most likely choose time and cost as their highest priorities.
In this case, I would make technical decisions that may not be the highest for long-term quality, but prioritise saving money, and getting to market quicker.
Or another example is a large enterprise organisation may choose time and quality as their highest priorities. Then I would add more developers to the project to try and get it done faster (although we all know doubling the devs doesn’t halve the time!). I would also put significant effort into making sure the system is developed to the highest quality standards.
I recommend being open and telling clients about any large technical trade-offs you recommend implementing.
For example, my agency once built a mobile application using Parse, a Platform-as-a-Service SaaS that allowed us to get things done really fast. I suggested this to the client and outlined the pros and cons of using a PaaS over building it from scratch, and we both agreed that for the startup it was best suited.
Facebook then acquired Parse shortly after, and about 12 months later Parse shut down its SaaS, and we had a few months to move platforms.
This sucked, but sh*t happens. Especially when you are relying on other services or libraries.
Even though it wasn’t a perfect situation, I was glad I involved my client in the original decision to use a PaaS, as they got to make an informed decision about the trade-offs involved.
Like most things, the more you practise the better you will get.
Identifying accidental technical debt is definitely one of those things, and this was a big role I played in my agency, and is a core responsibility of any technical lead or CTO.
Helping balance deliberate technical debt is often a little harder to learn, as just like balancing debt on a credit card, it can get out of control quite easily.
Just remember to involve your client in these conversations, as success is collaborative.