Thriving in a Legacy Codebase: From Chaos to Confidence
By Lombardelli Roméo, Software Engineer | 2025.04.02 | 6 min read

Introduction

Joining a new company or squad is always exciting—until you inherit a legacy codebase.

At Spendesk, my squad took ownership of an outdated, fragile, and mostly untested part of the web application that was far from modern web standards. The challenges were immediate: regressions in our product flows were triggering multiple support tickets per week, code was crashing during critical user operations (i.e., requests creation), and it was really hard for our engineering team to estimate the effort required for a ticket due to hidden technical complexity.

In the face of these issues, we took a structured approach to improve our workflow without stopping feature development. After a few months, this led to increased developer velocity with fewer bugs, stronger code confidence through testing, and better team collaboration. Here’s how we did it.


1. Diagnosing the Problem: Understanding the Legacy System

Before making changes, we needed to understand what we were dealing with. Our first steps:

Adding Observability

  • Implemented logging and monitoring to track errors and performance bottlenecks.
  • We introduced full test coverage for key logic functions to catch regressions in frequently failing features.
  • We started working towards better code coverage and quality through integration testing and the use of tools like Mock Service Worker (https://mswjs.io/)

Analyzing Technical Debt

We cataloged the biggest outdated elements in our codebase:

JavaScript instead of TypeScript → No type safety, harder debugging.

Redux for API calls → Unnecessary complexity, boilerplate-heavy.

Deprecated React class components → Slower performance, harder maintenance and no access to modern react features especially hooks.

Old styling practices (Sass, CSS files instead of Tailwind or CSS modules) → Inconsistent styles, difficult overrides.

Usage of deprecated libraries → Lodash, moment, axios, etc.

Lesson: Before jumping into refactoring, take time to measure and document the issues. Having clear metrics helps justify the investment and track progress.


2. Prioritizing Refactors for Maximum Impact

Refactoring everything at once wasn’t an option. We had to be strategic about what to tackle first.

Our Prioritization Framework

🔹 High Pain, Low Effort → Quick wins that improved developer & user experiences. These can usually be done by a single engineer and should fit into any downtime.

  • Adding integration tests to key product flows.
  • Migrating complex class components to functional ones.
  • Adding TypeScript types to component props and states.

🔹 Low Pain, Low Effort → Small, low-risk improvements we could tackle in spare cycles. (E.g., updating ESLint rules, code formatting)

  • Code formatting and style consistency.
  • Removing specific usages of deprecated libraries (lodash, moment, sass).
  • Migrating simple class components to functional ones.

🔹 High Pain, High Effort → Bigger initiatives that needed roadmap prioritization.

  • Migrating every file of our scope to TypeScript.
  • Adding tests on every part of the application.
  • Changing our data fetching strategy - moving from Redux-based API calls to React Query, bringing better loading states, error handling, and significantly reduced boilerplate code.

Physical representation of the framework

Lesson: Focus on changes that bring immediate value while laying the groundwork for long-term improvements. Every improvement counts. Even something as simple as correctly typing a function can reveal unneeded or unused code that can be removed. The end goal is not to have a larger codebase; in fact, it’s the opposite. We want just the right amount of code to keep our app running smoothly in production—code that is as simple and explicit as possible.


3. The Refactoring Mindset: Small Wins, Big Changes

Legacy code can feel overwhelming, but we adopted a progressive refactoring mindset:

Key Strategies

“Leave it better than you found it” – The ‘scout rule’ means that every time we touched a file, worked on a feature, or made any change at all, we refactored just a bit instead of rewriting everything. Before starting any work, ask yourself: Is there a refactor I could do to ease development? An hour of refactoring could prevent hours of problems down the line and make future development easier.

Introduce new patterns gradually – Rather than massive rewrites, we wrote new features using modern practices and phased out old ones. By using feature toggles, we could fork specific parts of the code, making it easier to remove untested legacy code after General Availability. ✔ Automate migrations – If you can, using codemods and scripts to systematically refactor large portions of the codebase can be highly effective.

Lesson: Small, incremental refactors reduce risk while keeping development moving. It also helps build a trusting relationship with customers and stakeholders. The ability to ship features on time, while also improving the state of the codebase is real asset for any squad.


4. The Payoff: Velocity, Stability, and Confidence

After a few months of focused effort, the results were clear:

🚀 Increased Developer Velocity – Less friction, fewer bugs, faster feature development.

🛡️ More Confidence in Code – Tests and TypeScript reduced regressions and errors.

🎯 Better Collaboration – A cleaner, more consistent codebase made it easier for engineers to contribute.

Lesson: Refactoring isn’t just about cleaner code—it’s about enabling engineers to move faster and build better.


Conclusion: Turning Legacy Code Into an Asset

Legacy code isn’t a curse—it’s an opportunity for transformation.

By focusing on diagnostics, prioritization, and incremental improvements, we transformed an outdated system into a more maintainable and scalable one. The key was not to seek perfection but progress.

If you’re dealing with legacy code, remember: start small, focus on impact, and embrace the process. Every improvement, no matter how minor, moves your codebase towards a better future.