Joining an Existing Project: How to Read and Understand the Codebase
What should you do when joining an existing project to understand the code?
- Start from entry points: Find how the app is run (e.g.
main.py,app.py, CLI, or framework entry). Follow one request or one user flow from start to finish. - Read the docs: README, CONTRIBUTING, ADR (Architecture Decision Records), onboarding docs, and runbooks. They explain intent, structure, and how to run/tests.
- Map the structure: See the top-level folders (e.g.
app/,tests/,scripts/) and the main modules. Identify where API routes, services, DB access, and config live. - Follow the data: Pick one feature and trace it: request → route → service → DB/API → response. Understand one path deeply before spreading out.
- Run it locally: Check out the repo, install deps, run the app and tests. Fix any setup issues; that forces you to learn the environment and tooling.
- Ask and pair: Use the team’s docs, Slack, or pairing sessions to clarify conventions, “why” decisions, and where to look next.
How do you read and understand existing code effectively?
- One flow at a time: Don’t try to read everything. Pick one user story or one API and follow it through the stack (UI/API → service → DB/external calls).
- Use the IDE: Jump to definition, find references, and “go to caller” to see how a function is used. Use search (e.g. for a route path, class name, or config key) to find related code.
- Read tests: Unit and integration tests show how components are used and what behavior is expected. Test names and fixtures often document the domain.
- Read git history:
git log -p -- <file>or blame can show why code was added or changed. Look at PR descriptions and tickets if available. - Draw a sketch: A small diagram (modules, layers, main dependencies) helps you see boundaries and data flow.
- Refactor in your head (or in notes): “How would I name this? How would I split this?” That forces you to understand responsibilities and coupling.
What are the main areas to focus on when onboarding to a codebase?
- Architecture: Monolith vs services, layers (API, service, repository), and how components talk (sync/async, queues, events).
- Conventions: Naming, project layout, where new code goes, style (lint/format), and branching/review process.
- Dependencies: Core frameworks (FastAPI, Django, etc.), DB, caches, message brokers, and third-party APIs. Know how config and secrets are loaded.
- Testing: How to run tests, what’s mocked, and how to add or change tests.
- Deployment and env: How the app is built, deployed, and configured (env vars, feature flags).
- Pain points: Ask the team what’s fragile, what they’d like to change, and where most bugs appear. That tells you where to be careful and where you can help.
How do you avoid breaking things when you start changing code?
- Run tests first: Make sure the test suite passes before and after your changes. Run the subset that touches your area.
- Small changes: Prefer small, reviewable edits. One behavioral change per PR when possible.
- Match existing style: Follow naming, structure, and patterns already in the file or module. Don’t introduce new patterns without team agreement.
- Read related code: Before changing a function, see all call sites so you don’t break implicit contracts (e.g. return shape, side effects).
- Add tests for new behavior: If you fix a bug or add a feature, add or update tests so the next person (or you) doesn’t regress it.
- Ask for review: Use code review to catch mistakes and to learn team conventions and domain rules.
What habits help when joining an existing project?
- Take notes: Document structure, decisions, and “why” in a personal doc or wiki so you don’t forget and can onboard others later.
- Improve docs as you go: When you find missing or wrong docs, fix them in the same PR or a follow-up.
- Ask “why”: When something looks odd, ask. Often there’s a constraint or history that isn’t in the code.
- Contribute early: Fix a small bug, improve a comment, or add a test. It forces you to understand the flow and build trust.
- Resist big rewrites at first: Understand and ship small improvements before proposing large refactors. You’ll make better decisions with more context.
Interview angle
- “How do you get productive in an unfamiliar codebase?” - run it locally first, then follow one real request end to end through the layers. That single trace teaches you more about the actual architecture than any diagram. Then read the tests, which document intended behaviour.
- “What do you look at first?” - the README and any architecture notes, the dependency list (which tells you the real stack), the test suite, and recent git history to see where change is concentrated and who to ask.
- “How do you avoid breaking things early?” - start with a small, well-scoped change that touches something real, get it reviewed, and learn the team’s conventions from that feedback rather than from guessing.
- “When do you start suggesting changes?” - after you understand why the current shape exists. Proposing a rewrite in week one signals you’ve mistaken unfamiliarity for badness. Note what looks wrong, wait until you know the constraints, then raise it with evidence.