How we pick up an inherited codebase in the first week
Inheriting a running app with real users is its own discipline. The sequence we follow — get it running, map the data, name the scary part — before we write anything anyone would call a feature.
Most of what we get handed isn’t a blank page. It’s a running application with paying users, a database nobody fully remembers the shape of, and a founder who needs it to keep working while we change it. Over the years that’s become the work I find most interesting — and the work where a wrong first move costs the most.
The difference between a rescue that holds and one that quietly re-breaks a month later is almost always made in the first week.
Here’s the sequence we actually follow when we inherit a codebase, in the order we follow it.
Get it running locally before you read a line of logic
The first real test of how healthy a project is: how long does it take a new developer to get it running on their own machine? If there’s a README that works, that tells me the previous team cared. If it’s three days of guessing at environment variables and missing migrations, that tells me something too — and it’s the first thing we fix, because every future decision depends on being able to reproduce the system.
We don’t change behaviour in this phase. We just get to a green state: the app boots, the test suite runs (or we note that there isn’t one), and we can hit the main flows.
Map the data before the code
Code lies about intent; the schema rarely does. Before I trust any controller, I read the tables — what’s related to what, where the foreign keys are missing, which columns are clearly bolted on later. On a Laravel project that usually means reading the migrations in order, then comparing them to what’s actually in the database, because the two drift apart more often than people admit.
A quick query like this tells me more about a project’s history than an hour of reading controllers:
SELECT TABLE_NAME, TABLE_ROWS, CREATE_TIME
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
ORDER BY CREATE_TIME DESC;
The newest tables are usually where the last team was firefighting. That’s where the bugs live.
Name the one genuinely scary thing
Every inherited system has a part everyone tiptoes around. The billing calculation. The nightly sync. A migration nobody dares re-run. We make a point of finding it in the first week and saying it out loud to the client: this is the risky area, here’s why, and here’s how we’d approach changing it safely.
Make the first change small and boring
The temptation on a new codebase is to prove yourself with something impressive. We do the opposite. Our first shipped change is deliberately small — a bug fix, a copy change, something with an obvious before and after — because the real thing we’re testing isn’t our code, it’s the path to production. Can we deploy? Does anything break? How long is the feedback loop?
Once that path is proven and trusted, everything after it is faster. Skip this step and your first big change becomes the thing that also discovers the deploy is broken.
Then, and only then, plan the real work
By now we have a running app, a mental model of the data, a named risk, and a working deploy. That’s the point where we can give a client an honest plan instead of a hopeful one. Anything we’d estimated before this would have been a guess dressed up as a number.
None of this is glamorous. But the work that decides whether a rescue holds is done here, before we’ve written anything anyone would call a feature.