Getting AI to Explain a Codebase You Just Inherited
"Explain this codebase" returns a summary of your folder names that feels like understanding. Traversal questions — trace the launch path, slice one feature, find the seam between two architectures — turn a model into a fast reader, provided you verify every edge it claims.
Hessam Rastegari
Senior Android Developer · 12 years shipping Android
TL;DR — A model is a fast reader with no memory and no stakes: use it for traversal, never for judgement. Run the app first and anchor on visible behaviour, then ask for a launch-path trace, one vertical feature slice, and "show me two places that do the same thing differently" — which finds the seam between the old architecture and the new one, the most valuable day-one fact there is. It invents call paths confidently, can't see DI wiring / reflection / manifest entries / string routes, and will guess why the code is this way when it only knows what it does. It points; you verify.
The prompt that wastes your first day
"Explain this codebase."
What comes back is a description of your directory structure with plausible-sounding responsibilities
attached. :core:network handles networking. :feature:checkout implements checkout. All true, none of
it information you didn't have from ls.
The real problem isn't that it's useless — it's that reading it feels like learning. You come away with a mental model made entirely of module names, and the first time you need to change something you discover you know nothing about how anything connects.
The reframe
Treat the model as a fast reader with perfect patience and no accountability. It will read 4,000 lines and answer a specific question in fifteen seconds. It will also invent an answer with exactly the same confidence when it doesn't have one, and it will never be the one debugging the consequence.
Everything below follows from that: give it traversal work (find, trace, tabulate, compare) and keep judgement work (is this good, why is it here, what should change) for yourself.
Run the app first
Before reading a single file, install it and use it for twenty minutes. Write down what you can see and do: the tabs, the flows, the settings, what happens offline, what looks slow.
Now every question you ask has an anchor in observable behaviour. "How does the app get from the splash to the home feed?" is answerable and checkable. "What is the architecture?" is neither.
This ordering matters more than any prompt in this post. A codebase understood from the UI inward stays correct; a codebase understood from the architecture diagram outward tends to be a description of what someone once intended.
The five questions that pay off
1. The launch trace
Trace execution from process start to the first screen being rendered.
Name every file and function in order. Where DI, reflection or the
manifest is involved, say so and mark it UNVERIFIED.
One entry point understood completely is worth more than a map of everything, because it crosses every layer the app has: Application class, DI setup, a splash, routing, a ViewModel, a repository, a data source. That single path is the app's conventions.
The "mark it UNVERIFIED" instruction is doing real work — it makes the model's blind spots visible instead of letting them be smoothed over into a clean narrative.
2. A vertical slice
Here's the checkout feature. Find every file involved, from the button
tap to the network call to the DB write, and draw the call path.
Note anywhere two files do the same job.
Pick a feature that's important, not a simple one — the important ones are where the conventions were actually decided. When you can follow one slice end to end and predict where a new field would need to be threaded, you know the codebase well enough to make a small change safely. That's the real day-five goal, not a diagram.
3. The god objects
Compute this yourself and paste the result — it's a shell one-liner, and it's data the model can't get:
# the files everything depends on
grep -rho "import com\.yourapp\..*" --include=*.kt src | sort | uniq -c | sort -rn | head -20
# and the files that change constantly
git log --format=format: --name-only --since=1.year | sort | uniq -c | sort -rn | head -20
Then: "Here are the 20 most-imported and 20 most-changed files. For each, what is it and what's it doing in both lists?" Anything in both is your risk concentration — heavily depended upon and constantly moving.
4. Tabulate the monster class
Every inherited codebase has one. A 2,800-line SyncManager that nobody will explain.
Table for this class: public method | what state it touches |
what it calls out to | do you see any caller in the files I gave you.
A table is the right output format — it's checkable line by line, and it resists the narrative drift you get from prose. The last column is the gift: candidates for dead code. Candidates. Verify each with Find Usages, and remember it can't see callers from a manifest, a deep link, another module or another repo.
5. Find the seam
The highest-value question, and the one nobody asks:
Show me two places in this code that solve the same problem in
different ways. For each pair, guess which is older, from the APIs used.
Every inherited codebase has a fault line: RxJava on one side and Flow on the other, Fragments here and Compose there, two HTTP clients, two navigation approaches, two DI containers. Knowing where the seam runs tells you which conventions to follow in which files — and following the wrong one is the classic new-joiner PR that gets a lot of comments and teaches you nothing pleasant.
Where it lies, in this exact task
1. It invents call paths. This is the big one. It will state that SyncManager calls
TokenRefresher.refresh() because that is what codebases like yours usually do — a strong prior, not an
observation. The invented edges are always plausible, which is what makes them expensive.
Hard rule: it points, you verify. Every edge you're going to rely on gets a Find Usages or a
grep. This is fast, and it's the entire discipline — the model's value is that it tells you where to
look, and it collapses if you skip the looking.
2. It cannot see Android's hidden edges. DI graph wiring, reflection, Class.forName, manifest
<activity> and <receiver> entries, deep links, string-based navigation routes, WorkManager workers
resolved by name, ProGuard keep rules. That list is not incidental — those are precisely the mechanisms
by which Android apps connect things, so the parts of the graph it's blindest to are the parts that hold
the app together.
3. It explains what, then guesses why. "This retry logic was likely added for poor network
conditions." Maybe. Or it was added because one enterprise customer's proxy broke in 2022 and there's a
contract. The why is business history that exists in git log, in Jira, and in one person's memory —
never in the code. Treat every causal claim as fiction until sourced.
Better: point it at real history. git log -S "retryOnConnectionFailure" --format="%h %ad %s" and ask it
to summarise the commit messages. Now it's reading evidence instead of guessing.
4. It will never say "this is a mess." Ask it to assess the architecture and you'll get diplomatic observations about trade-offs. Judgement is yours.
Write it down as you go
Keep a NOTES.md in the repo from day one. Two rules: only things you verified, and each entry says
how you verified it.
## Launch path (verified 2026-12-02, via debugger)
App → SplashActivity → AuthGate.check() → MainActivity → NavHost("home")
Auth token read in AuthGate; refresh happens in an OkHttp Authenticator
(not in the launch path — found via Find Usages, not obvious from the code).
## Seam
Feature modules built before 2023 use RxJava + Fragments (:feature:account,
:feature:legacy-cart). Everything after uses Flow + Compose. New code follows
the second. Confirmed with the module owners' commit dates.
Two things happen. In three weeks this is the onboarding document that never existed, and you become the person who wrote it. And more immediately: writing a trace down is how you discover which parts you only thought you understood. The gaps are obvious on the page and invisible in your head.
What to save for a human
The questions no model can answer, worth spending your limited access to the one remaining old-timer on:
- Why is this here? What broke that made someone build it?
- What's the thing everyone is scared to touch, and what happened last time someone did?
- Which of these tests actually catch bugs, and which are theatre?
- What did the last three people who tried to fix this get wrong?
- What's the political constraint I can't see in the code?
Knowing which questions a model cannot answer — and therefore which ones to spend a human's time on — is most of the skill here.
The rule
It gets you to the right file five times faster. It does not get you to the right conclusion. Use it to traverse, verify every edge it claims, write down only what you confirmed — and spend the time you saved on the questions that only a person who was there can answer.