Vetting & Technical Assessment · 4 min read

Assessing Technical Debt in an Inherited Codebase

How to evaluate a codebase you did not write, which signals predict future pain, and how to brief an engineer joining a system nobody fully understands.

Assess an inherited codebase by measuring change cost rather than counting issues: how long a small change takes, how often it breaks something unrelated, and how much of the system one person must understand to make it safely. Those three predict future pain better than any static metric.

Why issue counts mislead

The instinct when inheriting a codebase is to run static analysis and count what comes back. Thousands of warnings feels like a diagnosis. It is not a useful one, because the relationship between static issues and actual difficulty of change is weak. Plenty of ugly code is perfectly safe to modify, and plenty of clean-looking code is a minefield because of coupling nothing in the file reveals.

What actually costs money is change cost: how long it takes to make a small modification safely, and how often doing so breaks something apparently unrelated. A codebase where a one-line change requires understanding four services is expensive regardless of how it scores on any linter, and a codebase with inconsistent formatting but clean boundaries is usually fine.

This matters for hiring as much as for planning. An engineer evaluating whether to take on a system, or a team deciding how many people a migration needs, is answering a change-cost question. Framing the assessment that way from the start produces a far more useful picture than a report full of counts.

Signals that genuinely predict future pain

  • A one-line change requires understanding several services or modules to make safely
  • Test failures are routinely ignored because they are known to be flaky
  • Nobody can explain why a particular component exists, only that removing it breaks things
  • The deployment process has manual steps that one specific person performs
  • Configuration and business logic are interleaved, so behaviour cannot be read from code
  • The same concept has three names in three places, none of them documented
  • Recent commits cluster in a small number of files that everything else depends on

How to run the assessment in a week

Start by making a small real change rather than by reading. Pick something genuinely trivial, take it all the way to production, and time it honestly including the parts spent waiting or asking. That single number tells you more than a week of reading would, and it surfaces every broken step in the pipeline while the stakes are low.

Then look at the change history rather than the current state. Which files change most often, and which changes were followed within a week by a fix? That pattern identifies the parts of the system where modifications reliably go wrong, which is a far better target for investment than whichever module looks worst on inspection.

Finally, talk to whoever has been there longest and ask what they avoid touching. Every long-lived system has areas the team routes around, and those areas are almost never documented as risky. This is the single highest-yield question in the whole assessment and it takes ten minutes.

Briefing an engineer joining the system

An engineer joining an inherited codebase needs three things that are rarely written down. First, an honest map of which parts are safe to change and which are not, including the ones the team avoids for reasons nobody remembers. Presenting the system as cleaner than it is guarantees an unpleasant discovery in week two and damages trust immediately.

Second, the decision history behind the odd choices. Most surprising structures had a reason at the time, and knowing the reason is the difference between working with the design and fighting it. Where the reason is genuinely lost, saying so is better than inventing a rationalisation.

Third, a clear statement of what they are and are not expected to fix. Engineers joining a messy system will find many things they could improve, and without an explicit boundary they will either sprawl into refactoring or feel obstructed when told not to. Naming the boundary up front converts a source of friction into a shared understanding.

Turning the assessment into a plan

An assessment that produces a list of problems is only half useful, because every inherited codebase has more problems than anyone will ever fix. The output that actually changes decisions is a ranking by expected cost, which means estimating how often each area will need to change and how much each change currently costs.

Areas that are messy and never change are not worth touching, however uncomfortable they look. Areas that are moderately messy and change weekly are where the money goes, and they are frequently unremarkable on inspection. This is why change history matters more than code review when prioritising: it tells you where the future work will actually land.

Present the plan as a small number of targeted interventions with stated expected savings, rather than as a refactoring programme. 'Extracting this module reduces the blast radius of the changes we make most often' is a proposal a non-engineering stakeholder can evaluate. 'The codebase needs cleaning up' is not, and it is why so many technical debt proposals are declined.

Part of the Vetting & Technical Assessment cluster · Read the pillar page

More in Vetting & Technical Assessment

Frequently asked questions

How do I assess technical debt in a codebase I did not write?

Measure change cost rather than counting static issues: how long a small change takes to ship safely, how often it breaks something unrelated, and how much of the system one person must understand to do it.

Are static analysis issue counts useful?

Weakly. The relationship between static issues and actual difficulty of change is poor. Ugly code with clean boundaries is often safe, while tidy-looking code with hidden coupling can be expensive to modify.

What is the fastest useful assessment?

Make a small real change and ship it to production, timing the whole process honestly. That single number reveals more than a week of reading, and it surfaces broken pipeline steps while the stakes are low.

What should I tell an engineer joining a messy codebase?

An honest map of what is safe to change, the decision history behind the odd choices, and an explicit boundary on what they are expected to fix. Presenting it as cleaner than it is destroys trust in week two.

Which files should I look at first?

The ones that change most often, and particularly changes that were followed within a week by a fix. That pattern identifies where modifications reliably go wrong, which is a better investment target than whatever looks worst on inspection.