Technical Debt is the extra cost of current work caused by taking a shortcut in the past. Technical debt often gets a bad rep. It is often treated as symptom of a poorly run organization. Though there are some factual nuances to this view, it misses a key truth. Technical debt isn’t inherently good or bad - it is a tradeoff. Technical debt in your organization is a sign that earlier engineers made tradeoffs that may have been appropriate for the time.
It’s often a sign of great engineering to think carefully about these longer term tradeoffs and make good choices. The reality is, if we don’t take on technical debt, it’s very hard to get anything done. Projects are completed with Stepping Stones not Milestones.
However, we should avoid irresponsibly taking on technical debt lazily or on impulse. If we take it on irresponsibly, we’re setting ourselves up in the future for bugs, slower product development, and frustration. Conversely, when we accrue technical debt with intentionality, we’ll uncover unknown unknowns, plan for the future, and get to better results.
Here, intentionality means stopping and thinking deeply about the short and long term consequences of a decision. It means acknowledging that not everything can be solved at once. It means stopping and using the skill and taste we’ve gained from engineering experience to prioritize effectively. To effectively use technical debt, we must do it with intentionality!
Aside: Monetary Debt
Like monetary debt, technical debt can be good or bad.
We try to teach our kids about the virtues of patience and delayed gratification. Conversely, we try to drive home the perils of debt (delayed costs). The main reason we drive this point home for kids and young adults is because many are impulsive and lack intentionality when making these decisions (ooh a marshmallow!).
As adults, we’re (hopefully) more capable of long term thinking and managing impulsivity. We’re much more capable of responsibly taking on financial debt. We check interest rates and loan terms. We evaluate how useful the thing we want to buy is.
For example, a mortgage with a good interest rate within your stable income range can be a financially effective way to live in a great home earlier in your life, especially if you and your family take a personal interest the joys of home ownership and improvement (note: not financial advice).
Taking on Technical Debt Intentionally
Like monetary debt, technical debt is a useful tool for making progress. We often think of technical debt in the context of regret of our past actions or a chance to hate on our predecessors, but it’s important to remember that technical debt can just be a sign that someone earlier made an intentional decision to defer an investment.
The key piece is intentionality. When writing up a pull request, the spec for a component, or even a quarterly plan for the team - it’s impossible to fit everything in. Taking shortcuts is critical to having achievable goals, but knowing what debt is created allows us to make this decision responsibly. It’s worth a little extra effort to understand the shape and size of the debt, even if it’s not (yet) worth the larger effort to cut it down.
Learning how to intentionally evaluate shortcuts and technical debt is an art that comes with experience and reflection. Evaluating debt and communicating effectively really shows off your individual engineering excellence, whether you’re an IC making a pull request, or a CTO making the company’s 2 year plan.
Investments and Debts grow over time. When we take out a “loan” on technical debt to invest in another area, we must make sure our investment grows faster than the loan! The factors that affect your interest rates vary from system to system, company to company, team to team, case by case. Companies and leaders that make their values clear are great for this reason. It is a sign of strong engineering leadership to be able to make these sorts of intentional tradeoffs and convey the reasoning to peers.
There’s no one-size-fits-all formula to evaluating your interest rates, but here are some themes.
Resourcing Shifts: Positive resourcing shifts can make it easier to pay off the debt in the future. For example, if our team is expected to grow over the next few months or a separate foundational effort will make it easier to execute improvements with our existing team.
Gaining Insight: Building a simpler version of a system can expose unknown unknowns. Prototyping can help you do user testing. Building an intentionally stunted version of the system can allow you to learn what you can in v1 so that v2 can be the best.
External Urgency: Debt may be appropriate if there is an urgency to the value of the change. By seizing the moment, your team can gain momentum and confidence that can outweigh the cost. In a fast-paced competitive market, delay could even lead to losing an opportunity. It’s up to you to evaluate the size of opportunity and the size of the debt. Seize the moment if your gains would compound faster than your debts.
Red Flags on Tech Debt Loans
Impulsiveness / Laziness: Taking on technical debt impulsively is like taking a loan without knowing your interest rate. Consider the costs/benefits of your shortcuts. Make sure you understand how to deploy future investment to pay off the debt. Encourage and mentor newer engineers to take time to make these evaluations. Certainty and Effort are a tradeoff. Follow the Pareto principle - get 80% of certainty with 20% of effort.
Easy to fix today. Hard to fix in 6 months: This is a sign that your interest rates are very high. For example, beware of adding an anti-pattern that’s likely to be copy-pasta’d across codebase. Beware of technical debt that is likely to be split over a team boundary. It’s ok to add anti-patterns with intention, but pay close attention to how likely they are to grow over time.
Techniques for taking on technical debt effectively
Clean Interfaces: Messy code behind a clean interfaces usually achieves better interest rates than clean code behind a messy interface. Technical debt behind a clean interface can be rewritten and improved incrementally. Technical debt across an interface requires organizational coordination, rollouts, redeployments, and are harder to fix later.
Filing Follow-Up Tasks: File well-written follow up docs/tasks when taking debt intentionally. This forces you to understand the size and scope of the debt. The debt is more easily handed off to other engineers, or to other teams when handing off ownership. On a day-to-day level, file detailed follow-up tasks during code review. It proves to other engineers that you think about the interest rates, allowing you to make incremental progress. Reviewers are are more likely to accept a change when you’ve shown you’ve thought about the consequences.
Design for Rewrite: When creating v1 of a system, plan for it to be rewritten. All useful systems are rewritten many times! Start creating a v2 spec for the system while implementing and discovering unknown unknowns in v1. Knowing that a v2 spec is in the works makes it easier to take the right shortcuts on v1, avoiding work that may never become necessary. Remember what makes v1 good while designing v2 to overcome its shortcomings (avoiding Second-system-syndrome). Rewrites are a sign of success - they mean the system has value. It might feel bad, but it’s ok! v1 code might get deleted, but v1 ideas will live on.
Take on technical debt with intentionality. By prioritizing clean interfaces over clean implementations, designing for rewrite, and filing follow up tasks, we can accelerate projects, and end up with better results.
Here at Convex
Here at Convex, we value this philosophy. We know that data integrity is the bedrock of trust in our product, so we don’t take on debt that compromises it. We believe very deeply in the value and composability of clean, simple abstractions, so we take the time to develop APIs we think will stand the test of time. However, we routinely build and deploy simpler or less-efficient prototypes of systems that are designed for rewrite.
One example of technical debt we intentionally accrued was when we implemented deployment prewarming, a feature that ensures new deployments are returned from
npx convex init almost instantaneously. We designed, implemented, launched, and planned a rewrite for this feature in just two days. The solution was correct but inefficient. The quick turnaround unblocked an important partner and allowed us to evaluate how customers were using the platform, which was a win for the business. We’re willing to take a moment to understand and evaluate our technical debt so we can move fast where it matters.
We are hiring! If you engineer intentionally, balance short and long term thinking, love making prototypes, and love replacing prototypes as a compliment to the original - you might really enjoy it here. Check out our positions at https://www.convex.dev/jobs - or shoot me a message. We would love to hear from you.
If you liked this post, check out some of our others
- The platform you need, when you need it
- A tale of three rust codebases
- Introducing Fast5
- Randomized Testing
Thanks to Emma Forman Ling (They/Them) and James Cowling (He/Him) for comments and review.