Swimming in Tech Debt — Practical Techniques to Keep Your Team from Drowning in Its Codebase | Lou Franco
BONUS: Swimming in Tech Debt — Practical Techniques to Keep Your Team from Drowning in Its Codebase In this fascinating conversation, veteran software engineer and author Lou Franco shares hard-won lessons from decades at startups, Trello, and Atlassian. We explore his book "Swimming in Tech Debt," diving deep into the 8 Questions framework for evaluating tech debt decisions, personal practices that compound over time, team-level strategies for systematic improvement, and leadership approaches that balance velocity with sustainability. Lou reveals why tech debt is often the result of success, how to navigate the spectrum between ignoring debt and rewriting too much, and practical techniques individuals, teams, and leaders can use starting today. The Exit Interview That Changed Everything "We didn't go slower by paying tech debt. We went actually faster, because we were constantly in that code, and now we didn't have to run into problems." — Lou Franco Lou's understanding of tech debt crystallized during an exit interview at Atalasoft, a small startup where he'd spent years. An engineer leaving the company confronted him: "You guys don't care about tech debt." Lou had been focused on shipping features, believing that paying tech debt would slow them down. But this engineer told a different story — when they finally fixed their terrible build and installation system, they actually sped up. They were constantly touching that code, and removing the friction made everything easier. This moment revealed a fundamental truth: tech debt isn't just about code quality or engineering pride. It's about velocity, momentum, and the ability to move fast sustainably. Lou carried this lesson through his career at Trello (where he learned the dangers of rewriting too much) and Atlassian (where he saw enterprise-scale tech debt management). These experiences became the foundation for "Swimming in Tech Debt." Tech Debt Is the Result of Success "Tech debt is often the result of success. Unsuccessful projects don't have tech debt." — Lou Franco This reframes the entire conversation about tech debt. Failed products don't accumulate debt — they disappear before it matters. Tech debt emerges when your code survives long enough to outlive its original assumptions, when your user base grows beyond initial expectations, when your team scales faster than your architecture anticipated. At Atalasoft, they built for 10 users and got 100. At Trello, mobile usage exploded beyond their web-first assumptions. Success creates tech debt by changing the context in which code operates. This means tech debt conversations should happen at different intensities depending on where you are in the product lifecycle. Early startups pursuing product-market fit should minimize tech debt investments — move fast, learn, potentially throw away the code. Growth-stage companies need balanced approaches. Mature products benefit significantly from tech debt investments because operational efficiency compounds over years. Understanding this lifecycle perspective helps teams make appropriate decisions rather than applying one-size-fits-all rules. The 8 Questions Framework for Tech Debt Decisions "Those 8 questions guide you to what you should do. If it's risky, has regressions, and you don't even know if it's gonna work, this is when you're gonna do a project spike." — Lou Franco Lou introduces a systematic framework for evaluating whether to pay tech debt, inspired by Bob Moesta's push-pull forces from product management. The 8 questions create a complete picture: Visibility — Will people outside the team understand what we're doing? Alignment — Does this match our engineering values and target architecture? Resistance — How hard is this code to work with right now? Volatility — How often do we touch this code? Regression Risk — What's the chance we'll introduce new problems? Project Size — How big is this to fix? Estimate Risk — How uncertain are we about the effort required? Outcome Uncertainty — How confident are we the fix will actually improve things? High volatility and high resistance with low regression risk? Pay the debt now. High regression risk with no tests? Write tests first, then reassess. Uncertain outcomes on a big project? Do a spike or proof of concept. The framework prevents both extremes — ignoring costly debt and undertaking risky rewrites without proper preparation. Personal Practices That Compound Daily "When I sit down at my desk, the first thing I do is I pay a little tech debt. I'm looking at code, I'm about to change it, do I even understand it? Am I having some kind of resistance to it? Put in a little helpful comment, maybe a little refactoring." — Lou Franco Lou shares personal habits that create compounding improvements over time. Start each coding session by paying a small amount of tech debt in the area you're about to work — add a clarifying comment, extract a confusing variable, improve a function name. This warms you up, reduces friction for your actual work, and leaves the code slightly better than you found it. The clean-as-you-go philosophy means tech debt never accumulates faster than you can manage it. But Lou's most powerful practice comes at the end of each session: mutation testing by hand. Before finishing for the day, deliberately break something — change a plus to minus, a less-than to less-than-or-equal. See if tests catch it. Often they don't, revealing gaps in test coverage. The key insight: don't fix it immediately. Leave that failing test as the bridge to tomorrow's coding session. It connects today's momentum to tomorrow's work, ensuring you always start with context and purpose rather than cold-starting each day. Mutation Testing: Breaking Things on Purpose "Before I'm done working on a coding session, I break something on purpose. I'll change a plus to a minus, a less than to a less than equals, and see if tests break. A lot of times tests don't break. Now you've found a problem in your test." — Lou Franco Manual mutation testing — deliberately breaking code to verify tests catch the break — reveals a critical gap in most test suites. You can have 100% code coverage and still have untested behavior. A line of code that's executed during tests isn't necessarily tested — the test might not actually verify what that line does. By changing operators, flipping booleans, or altering constants, you discover whether your tests protect against actual logic errors or just exercise code paths. Lou recommends doing this manually as part of your daily practice, but automated tools exist for systematic discovery: Stryker (for JavaScript, C#, Scala) and MutMut (for Python) can mutate your entire codebase and report which mutations survive uncaught. This isn't just about test quality — it's about understanding what your code actually does and building confidence that changes won't introduce subtle bugs. Team-Level Practices: Budgets, Backlogs, and Target Architecture "Create a target architecture document — where would we be if we started over today? Every PR is an opportunity to move slightly toward that target." — Lou Franco At the team level, Lou advocates for three interconnected practices. First, create a target architecture document that describes where you'd be if starting fresh today — not a detailed design, but architectural patterns, technology choices, and structural principles that represent current best practices. This isn't a rewrite plan; it's a North Star. Every pull request becomes an opportunity to move incrementally toward that target when touching relevant code. Second, establish a budget split between PM-led feature work and engineering-led tech debt work — perhaps 80/20 or whatever ratio fits your product lifecycle stage. This creates predictable capacity for tech debt without requiring constant negotiation. Third, hold quarterly tech debt backlog meetings separate from sprint planning. Treat this backlog like PMs treat product discovery — explore options, estimate impacts, prioritize based on the 8 Questions framework. Some items fit in sprints; others require dedicated engineers for a quarter or two. This systematic approach prevents tech debt from being perpetually deprioritized while avoiding the opposite extreme of engineers disappearing into six-month "improvement" projects with no visible progress. The Atlassian Five-Alarm Fire "The Atlassian CTO's 'five-alarm fire' — stopping all feature development to focus on reliability. I reduced sync errors by 75% during that initiative." — Lou Franco Lou shares a powerful example of leadership-driven tech debt management at scale. The Atlassian CTO called a "five-alarm fire" — halting all feature development across the company to focus exclusively on reliability and tech debt. This wasn't panic; it was strategic recognition that accumulated debt threatened the business. Lou worked on reducing sync errors, achieving a 75% reduction during this focused period. The initiative demonstrated several leadership principles: willingness to make hard calls that stop revenue-generating feature work, clear communication of why reliability matters strategically, trust that teams will use the time wisely, and commitment to see it through despite pressure to resume features. This level of intervention is rare and shouldn't be frequent, but it shows what's possible when leadership truly prioritizes tech debt. More commonly, leaders should express product lifecycle constraints (startup urgency vs. mature product stability), give teams autonomy to find appropriate projects within those constraints, and require accountability through visible metrics and dashboards that show progress. The Rewrite Trap: Why Big Rewrites Usually Fail "A system that took 10 years to write has implicit knowledge that can't be replicated in 6 months. I'm mostly gonna advocate for piecemeal migrations along the way, reducing the size of the problem over time." — Lou Franco Lou lived through Trello's iOS navigation rewrite — a classic example of throwing away working code to start fresh, only to discover all the edge cases, implicit behaviors, and user expectations baked into the "old" system. A codebase that evolved over several years contains implicit knowledge — user workflows, edge case handling, performance optimizations, and subtle behaviors that users rely on even if they never explicitly requested them. Attempting to rewrite this in six months inevitably misses critical details. Lou strongly advocates for piecemeal migrations instead. The Trello "Decaffeinate Project" exemplifies this approach — migrating from CoffeeScript to TypeScript incrementally, with public dashboards showing the percentage remaining, interoperable technologies allowing gradual transition, and the ability to pause or reverse if needed. Keep both systems running in parallel during migrations. Use runtime observability to verify new code behaves identically to old code. Reduce the problem size steadily over months rather than attempting big-bang replacements. The only exception: sometimes keeping parallel systems requires scaffolding that creates its own complexity, so evaluate whether piecemeal migration is actually simpler or if you're better off living with the current system. Making Tech Debt Visible Through Dashboards "Put up a dashboard, showing it happen. Make invisible internal improvements visible through metrics engineering leadership understands." — Lou Franco One of tech debt's biggest challenges is invisibility — non-technical stakeholders can't see the improvement from refactoring or test coverage. Lou learned to make tech debt work visible through dashboards and metrics. The Decaffeinate Project tracked percentage of CoffeeScript files remaining, providing a clear progress indicator anyone could understand. When reducing sync errors, Lou created dashboards showing error rates declining over time. These visualizations serve multiple purposes: they demonstrate value to leadership, create accountability for engineering teams, build momentum as progress becomes visible, and help teams celebrate wins that would otherwise go unnoticed. The key is choosing metrics that matter to the business — error rates, page load times, deployment frequency, mean time to recovery — rather than pure code quality metrics like cyclomatic complexity that don't translate outside engineering. Connect tech debt work to customer experience, reliability, or developer productivity in ways leadership can see and value. Onboarding as a Tech Debt Opportunity "Unit testing is a really great way to learn a system. It's like an executable specification that's helping you prove that you understand the system." — Lou Franco Lou identifies onboarding as an underutilized opportunity for tech debt reduction. When new engineers join, they need to learn the codebase. Rather than just reading code or shadowing, Lou suggests having them write unit tests in areas they're learning. This serves dual purposes: tests are executable specifications that prove understanding of system behavior, and they create safety nets in areas that likely lack coverage (otherwise, why would new engineers be confused by the code?). The new engineer gets hands-on learning, the team gets better test coverage, and everyone wins. This practice also surfaces confusing code — if new engineers struggle to understand what to test, that's a signal the code needs clarifying comments, better naming, or refactoring. Make onboarding a systematic tech debt reduction opportunity rather than passive knowledge transfer. Leadership's Role: Constraints, Autonomy, and Accountability "Leadership needs to express the constraints. Tell the team what you're feeling about tech debt at a high level, and what you think generally is the appropriate amount of time to be spent on it. Then give them autonomy." — Lou Franco Lou distills leadership's role in tech debt management to three elements. First, express constraints — communicate where you believe the product is in its lifecycle (early startup, rapid growth, mature cash cow) and what that means for tech debt tolerance. Are we pursuing product-market fit where code might be thrown away? Are we scaling a proven product where reliability matters? Are we maintaining a stable system where operational efficiency pays dividends? These constraints help teams make appropriate trade-offs. Second, give autonomy — once constraints are clear, trust teams to identify specific tech debt projects that fit those constraints. Engineers understand the codebase's pain points better than leaders do. Third, require accountability — teams must make their work visible through dashboards, metrics, and regular updates. Autonomy without accountability becomes invisible engineering projects that might not deliver value. Accountability without autonomy becomes micromanagement that wastes engineering judgment. The balance creates space for teams to make smart decisions while keeping leadership informed and confident in the investment. AI and the Future of Tech Debt "I really do AI-assisted software engineering. And by that, I mean I 100% review every single line of that code. I write the tests, and all the code is as I would have written it, it's just a lot faster. Developers are still responsible for it. Read the code." — Lou Franco Lou has a chapter about AI in his book, addressing the elephant in the room: will AI-generated code create massive tech debt? His answer is nuanced. AI can accelerate development tremendously if used correctly — Lou uses it extensively but reviews every single line, writes all tests himself, and ensures the code matches what he would have written manually. The problem emerges with "vibe coders" — non-developers using AI to generate code they don't understand, creating unmaintainable messes that become someone else's problem. Developers remain responsible for all code, regardless of how it's generated. This means you must read and understand AI-generated code, not blindly accept it. Lou also raises supply chain security concerns — dependencies can contain malicious code, and AI might introduce vulnerabilities developers miss. His recommendation: stay six months behind on dependency updates, let others discover the problems first, and consider separate sandboxed development machines to limit security exposure. AI is a powerful tool, but it doesn't eliminate the need for engineering judgment, testing discipline, or code review practices. The Style Guide Beyond Formatting "Have a style guide that goes beyond formatting to include target architecture. This is the kind of code we want to write going forward." — Lou Franco Lou advocates for style guides that extend beyond tabs-versus-spaces formatting rules to include architectural guidance. Document patterns you want to move toward: how should components be structured, what state management approaches do we prefer, how should we handle errors, what testing patterns should we follow? This creates a shared understanding of the target architecture without requiring a massive design document. When reviewing pull requests, teams can reference the style guide to explain why certain approaches align with where the codebase is headed versus perpetuating old patterns. This makes tech debt conversations less personal and more objective — it's not about criticizing someone's code, it's about aligning with team standards and strategic direction. The style guide becomes a living document that evolves as the team learns and technology changes, capturing collective wisdom about what good code looks like in your specific context. Recommended Resources Some of the resources mentioned in this episode include: Steve Blank's Four Steps To Epiphany The podcast episode with Bernie Maloney where we discuss the critical difference between "enterprise" and "startup". And Geoffrey Moore's Crossing the Chasm, and Dealing with Darwin. About Lou Franco Lou Franco is a veteran software engineer and author of Swimming in Tech Debt. With decades of experience at startups, as well as Trello, and Atlassian, he's seen both sides of debt—as coder and leader. Today, he advises teams on engineering practices, helping them turn messy codebases into momentum. You can link with Lou Franco on LinkedIn and learn more at LouFranco.com.