Maintaining a codebase you didn't write
What you do in the first three weeks when you inherit a system in production, before touching a single line.
The first reaction to inherited code is always the same: you want to rewrite everything. The naming is inconsistent, the tests are missing, the dependencies are three years behind. Worst of all, the rewrite looks reasonable — you know the business, you have the modern stack, it will be faster.
It will not be faster. A system in production holds years of decisions, most of them written down nowhere. Rewriting means rediscovering them one by one, in production, with customers.
Week 1: read, change nothing
The rule is strict: no modification during the first week, not even a typo. Only read, run, take notes.
What I look for, in order:
- How it starts. If I cannot run the system on my machine within a day, that is the first problem to solve, before anything else.
- Where the money flows. Billing, payment, pricing: these are the areas where a mistake costs immediately. I locate them and leave them alone.
- What runs at night. Scheduled jobs, imports, exports. The parts nobody watches and that break silently.
- What changed recently. The repository history tells you where today's problems are better than any audit.
By the end of the week I have a map. Not a quality report — a map: safe zones, risky zones, unknown zones.
Week 2: put up a net
Still no functional change. We install what will let us make one without fear.
Characterisation tests on the risky zones: you do not test what the code should do, you freeze what it does today. If a fee calculation gives 4,950 for 5,000, the test says 4,950. Maybe it is a bug; this is not the moment to fix it, it is the moment to know.
A centralised error log, if there is none. You cannot maintain a system whose failures you cannot see.
A staging environment with realistic data. Half of inherited bugs only reproduce with real data — the kind typed by hand in 2016 with a comma instead of a point.
Week 3: the first change
Small, useful, visible. A known, annoying, low-risk bug. The goal is not the fix; it is to go through the whole chain — change, review, test, deploy — once, and discover what is missing at each step.
That is where you learn that deployment is done by hand from one person's laptop, or that migrations are not versioned. These discoveries are worth more than three weeks of rewriting.
What you tell the team
Inherited code was written by people who had less time, less information and constraints we do not know. Contempt for the previous code is the surest way to lose the people who wrote it — and they are often the ones who know why it is the way it is.
I set a simple rule: you may say "I don't understand why it's done this way". You do not say "this is nonsense". The first sentence opens a conversation. The second closes it.
When to rewrite, then
When the map is complete, the net is in place, and one specific area costs more to maintain than to replace. Module by module, never wholesale. With the characterisation tests as the contract: the new module must return 4,950 until someone has decided, in writing, that 5,000 is what it should be.
The system you inherit is not a problem to erase. It is the only reliable documentation of what the business actually needs to do.
Read next
A deadline is negotiated on scope, never on quality
What gets sacrificed when you agree to ship faster, and why it is never the part you think.
Hiring a backend engineer without asking them to invert a binary tree
An exercise drawn from the product, reviewed by two people, with a grid written before the interview. What changes, and what it costs.
FastAPI in production: what actually matters
Five settings you won't find in any tutorial, and that separate an API that runs from an API you can operate.