Move Fast and Improve Things | Part 2: How to Avoid Unnecessary Optimization
Daniel McGrath is the Director of Engineering at Truebill, the DC-based fin-tech startup famous for canceling unwanted subscriptions. In this three-part series, we interview him about how he balances technical debt and product development while building a popular consumer tech product with a growing user-base.
- In Part I, we talked about what technical debt is and how you can tackle it (or better yet – avoid it). Daniel provides tips for tech teams to pay off technical debt without getting bogged down by it.
- In this part, we talk about how tech teams can avoid wasting time on premature optimization. Daniel shares advice for tech leaders to strike the right balance between perfection and delivery, without sacrificing the quality of the end-product.
- In Part III, we’ll focus on debugging, and how engineering team leaders can hire and structure their teams that resolve bugs and outages fast.
The conversation below has been edited for length and content.
Q: How can engineering teams avoid prematurely optimizing their code?
I’ve worked with many other engineers throughout my career. A common trait among engineers is to let Perfect become the enemy of Shipped.
As engineers, it’s easy to obsess over abstractions, small performance improvements, and clean code. But, as with technical debt, the impact might not be there.
In the past, I could easily spend a whole day putting together the perfect abstraction to make the code as clear as possible. Then I would put it out there and no one would use the feature and I would delete it after a year.
To avoid unnecessary optimization, define an SLA for yourself.
To avoid wasting time perfecting code that won’t get used, define a service-level agreement for what you’re building. For example, you could decide that no message should ever sit in a particular queue for longer than thirty seconds. That way, when you’re thinking about removing a for loop to shave off 20 milliseconds, you can make a better judgment on if that actually moves the needle on your SLA.
That’s not to say that you should write suboptimal code. You should write readable code. But sometimes the most performable code is not the most readable code. Monitoring is key here. If you defined that SLA above, put it in place as a Datadog monitor or something and if you stop meeting it, you can come back and re-evaluate. Don’t sacrifice developer readability for machine performance.
To create a team that doesn’t waste time with premature optimization, give them the autonomy to ask questions and define an MVP.
As a company, we invested a lot of time and effort upfront in a few things that were going to be really important for us. For example, we spent time upfront to figure out how we could model the core negotiation features of Truebill such that we don’t ever get into an invalid state. Our business is built around that, so it was important to get it right.
For other things, we’ve deleted so much code that I’m glad we didn’t spend an entire week on it. It wasn’t worth wasting time trying to figure out how to write code we weren’t sure would be around next year. That’s the question:
“Are people going to use this? Is this going to be code that people are touching every day?”
When we kick off something new we try to put a group of engineers around a project. Then we give them as much autonomy as possible to define the ideal iterations for the project. We want our engineers to be able to define:
- What does the MVP look like?
- What does the subsequent V2 look like?
That way we don’t spend three extra weeks building out features if the MVP didn’t catch on at all.
For more advice on debugging, optimization, and technical debt, check out Part 1 and 3 (coming soon).