The personal blog of Scott Polhemus

Managing technical debt in product-led engineering teams

Wednesday, May 15th 2024

Tech debt is the boogeyman of every fast-paced software team. It’s the malicious and secretive force lurking in the corners of your legacy codebase waiting to spring bugs and performance issues onto poor defenseless users when they least expect it, or the necessary evil you pay sacrifice to in service to the ever-demanding roll of forward progress.

As an engineer and as a leader, I’ve often been put in the position of balancing the harsh realities of time constraints and resource pressures squarely in opposition to the critical need to architect technical systems for the ability to scale well and be maintained by other engineers for the foreseeable future.

In other words:

“We need this feature and we need it now. Whatever it takes.

Wittingly or unwittingly, the decisions of the past (however prudent) will come back to bite you in the tuckus. Before long that rushed-out feature is in front of users and performing “well enough”, the engineer who developed it has moved on to something else and forgotten all about it, and the team is left maintaining a system that was haphazardly shipped out with the ever-optimistic line of thinking that “we’ll be able to revisit this later”.

This might come across a little salty, but most of the time this isn’t a bad thing. It’s how things get done. “Don’t let perfect be the enemy of the good” and all that. Engineers who take pride in their work want to deliver high quality software every time, so it can feel crappy to knowingly cut corners. However, having good judgement around when to invest time and resources into optimal system design and clean, well-documented program architecture is one of the things that separates high-performing engineers from the rest.

Be Realistic

Many engineers, once they’ve felt the pain of maintaining an old system, will attempt to address technical debt before it begins by ensuring that they are always practicing the careful, considered programming style that results in scalable and maintainable software.

The problem is that this can be incredibly time-consuming, often unpredictably so, and is in fact rather subjective. By spending more time than necessary on a task, this engineer might end up blocking other stakeholders from being able to verify and deliver on a feature within the expected timeline. And by prematurely fine-tuning their code before soliciting feedback from other engineers, they might also be missing out on insights or strategies from other team members that could influence their approach and save time.

Direct communication is absolutely critical. When planning new feature work, be realistic about what’s possible and give context to your team on what trade-offs are needed to prioritize expediency over quality. Nobody wants to release subpar work, but an excellent engineer can recognize that the value of delivering an impactful feature sooner and gaining insights and feedback along the way rather than investing an inordinate amount of engineering time for diminishing returns.

Be Strategic

So you’ve knowingly shipped a feature with tech debt, or (better yet) inherited some by taking on ownership of a pre-existing codebase. Now what? You’ve probably got a feature roadmap a mile long, a small scrappy team, and high expectations from leadership to deliver meaningful work with measurable improvements on a regular basis. How can you possibly address the growing pile of tech debt under those conditions?

One essential strategy is to document the technical debt in your team’s project management system, so that the work is clearly visible to everyone as an area that requires further investment. Without this actual artifact representing the work, it really only exists in your brain and can easily be brushed aside (let alone actually prioritized). Once you’ve written out some details about what should be improved, why the initial decisions were made and some ideas about how to mitigate the trade-offs, it becomes something that can be discussed and assessed and revisited with at least some prior context intact.

Be Tireless

Good engineering leaders will listen to their team when they hear about pain points in the codebase that require additional work. They’ll pay attention if developers on the team are communicating to them consistently about the impact of some technical debt.

It then becomes the challenge of the engineering lead to synthesize this information, propose specific initiatives to perform the necessary work, and get buy-in from stakeholders to fit it into planned work. This often comes in the form of designated “Flex Time” or some sort of rotating assignment where engineers are specifically asked to spend some amount of their time on engineering-driven initiatives at their own discretion.

It might feel like complaining to constantly bring up pain points and areas of tech debt that are already well documented, but each time you do you’re giving your leaders more ammunition to make the case for why this work is important and what the benefits of carving out time for it would be.


One last bit of advice: celebrate your wins in the battle against tech debt! Treat refactors and upgrades with the same level of respect you give to great new features and innovative technical feats. Nobody will be more excited to revel in these victories with you than your fellow engineers, because they feel the benefits of work like this in their everyday experience. Tech debt is a constant trade-off within software engineering, and even the most carefully-designed systems will incur this cost over time.

Improving the efficiency and effectiveness of your engineering team, or fixing a set of looming issues with the product itself before they become a problem, can be just as valuable as delivering net-new user-facing functionality. Learning to communicate the value of that work effectively and advocate for addressing technical debt is an excellent way for engineers to take some additional ownership and control over the work they produce.