From Bug Fixes to Feature Development: Working Effectively in Existing Codebases
Most professional software work is changing code somebody else wrote. A practical method for getting productive in an unfamiliar codebase quickly — and safely.
Starter draft. Reads well as a companion to the freelance section of the site — adjust as you see fit.
Greenfield work is the exception. Most development is modifying software that already exists, usually written by people who are no longer available to ask. Getting productive in an unfamiliar codebase is a skill in itself, and it is largely method rather than talent.
Here is the sequence I use, particularly on short freelance engagements where "productive this week" is the requirement.
1. Run it before you read it#
Before opening a single source file: get it running locally.
This is not a formality. Setting it up tells you what it depends on, how it is configured, what external services it needs and where the sharp edges are. It also produces the first genuinely useful artefact of the engagement — accurate setup notes, which frequently do not exist.
2. Read the data model#
For most business applications, the schema is the clearest available description of the domain. Table names, relationships and constraints tell you what the system believes about the world. The code tells you how it manipulates that; the schema tells you what it is.
Look for:
- Core entities and how they relate
- Columns that suggest history —
legacy_,_old,is_deprecated - Where constraints exist and where they only live in application code
3. Follow one request end to end#
Pick a single meaningful operation and trace it completely: route, controller, validation, business logic, data access, response. One trace teaches you the codebase's conventions faster than an hour of browsing.
Specifically, you learn: how routing works, where validation lives, whether there is a service layer, how errors are handled, and how the team names things.
4. Read the commit history#
git log on the files you are about to change is genuinely informative:
- Which parts change constantly (and are therefore risky)
- Which parts have not changed in years (and are therefore load-bearing)
- Commit messages referencing incidents, which explain the odd-looking code
# Most frequently changed files git log --format=format: --name-only | sort | uniq -c | sort -rn | head -20
5. Start with something small#
Do not begin with the largest item. Begin with a small, real change that goes all the way to production. It exercises the whole path — local setup, tests, review, deployment — and surfaces every obstacle while the stakes are low.
Making changes safely#
Match existing conventions. Even ones you dislike. A codebase with one consistent style is easier to work in than one with two competing styles, and your preferences are not worth the inconsistency. Raise a convention change as its own conversation.
Change as little as possible. In unfamiliar code, the risk is in the surroundings, not the fix. Reformatting a file alongside a one-line change makes the diff unreviewable and hides the actual change.
Write the test first where you can. In a codebase you do not know, a failing test that captures the bug is your proof — both that you understood it and that you fixed it.
Ask about the odd thing. If code looks wrong, assume there is a reason before concluding there is not. Sometimes there is not. Often there is, and it is a production incident from two years ago.
What to leave behind#
On a short engagement, the deliverable is not only the code:
- Notes on what you changed and why
- Anything you learned about setup or deployment that was not written down
- Problems you noticed but did not fix, so the team can decide about them
The team keeps maintaining this after you leave. Leaving them able to do that is part of the job.
The underlying attitude#
An unfamiliar codebase is not a mess to be corrected. It is a system that has been solving real problems, under real constraints, for longer than you have been looking at it. Treat it that way and you will be useful much faster.