Home • Articles • AI delivery • How do you understand an undocumented codebase?

How do you understand an undocumented codebase?

Inès, Bas and Ravi trace a software request through code, data and integrations.

TL;DR

Understand an undocumented codebase by tracing one real behaviour from an entry point to its data and external effects. Build a small evidence map of routes, jobs, storage, dependencies, tests and owners. Run the system, observe a request, follow definitions and references, and record what you verified separately from what you inferred. Avoid reading every file in order. The first useful result is a change map for one feature, including its risk boundaries and a safe place to test your understanding.

In this article
  1. Pick a behaviour that crosses the system
  2. Run the software before trusting its shape
  3. Build an evidence map, not an architecture fantasy
  4. Follow data and side effects to find the risk
  5. Use tests as questions about intent
  6. Ask people for exceptions, not a complete tour
  7. Make one safe change to test the map
  8. Turn the map into living documentation
  9. Frequently Asked Questions

6 min read

An undocumented codebase presents thousands of details without telling you which ones matter. Reading from the first file to the last creates familiarity but rarely creates a working model. The system’s real shape appears through behaviour: where input enters, which decisions it triggers, what state changes and what leaves the boundary.

The practical question is how do you understand an undocumented codebase without getting lost? Choose one important behaviour, observe it running, trace it through code and data, and write down evidence as you go. Expand the map only when the next task needs it.

Pick a behaviour that crosses the system

Start with a feature that matters and can be reproduced safely: sign in, create an order, import a file or run a scheduled job. A useful slice crosses enough layers to reveal structure without requiring the whole system.

Write a simple trace target:

  • Input: the request, event, command or scheduled trigger.
  • Decision: the rule that chooses what happens.
  • State: the records read or changed.
  • Effects: messages, files, calls or user-visible output.
  • Failure: one important way the behaviour can stop or become unsafe.

This gives your exploration a finish line. You are not trying to understand “the architecture.” You are trying to explain one observable path well enough to change or test it.

Run the software before trusting its shape

Repository structure suggests architecture, but names and folders can be stale. Run the smallest available environment and observe the chosen behaviour. Capture the route, command, log line, database change or event that proves what happened.

If the project does not run, treat that as the first finding. Record the failing command, environment assumptions and missing dependency. Do not silently rewrite setup while exploring; first preserve the evidence of the current state.

GitHub’s guide to navigating code shows how definitions, references and symbol search reveal relationships. Use these tools from the observed entry point. Follow calls only as far as needed to explain the behaviour.

An undocumented-codebase map tracing input through decisions, data, external effects and failure boundaries.
Trace a real behaviour from the outside inward, and label every connection as observed, verified or inferred.

Build an evidence map, not an architecture fantasy

Create a small table or diagram while tracing. For each node, record its role, the evidence you saw and the confidence of the connection. Distinguish direct observation from an inference based on names or static reading.

Comparison

Map itemEvidence exampleConfidence
HTTP route enters controllerRoute list and observed requestVerified
Service applies pricing ruleFocused test and code traceVerified
Queue retries after timeoutConfiguration onlyInferred until exercised
Table is owned by this serviceWrites found in repository searchProbable; check other systems

This honesty matters. An attractive diagram can create more false confidence than no documentation if guessed connections look authoritative. Use question marks and named follow-ups instead of filling every gap.

Follow data and side effects to find the risk

Control flow tells you what calls what. Data and effects tell you what can hurt. Identify persistent tables, caches, files, messages and external APIs touched by the behaviour. Search for every writer, not only the path you started with.

GitHub’s dependency graph documentation explains how manifests and dependency paths can reveal direct and transitive packages. That graph helps with package risk. It does not show business coupling, so also search for shared tables, event names and environment keys.

Pay attention to boundaries that make a change difficult to reverse: migrations, asynchronous work, calls to payment or identity services, destructive updates and hidden scheduled tasks. These become priority areas for tests and reviewer input.

Use tests as questions about intent

Tests can reveal expected behaviour, vocabulary and seams. They can also be stale or narrowly shaped around the implementation. Read the test name, setup, assertion and fixture together. Then run a focused test and alter a harmless condition to see whether it protects the behaviour you think it does.

Tests with broad mocks may hide the integration you need to understand. End-to-end tests may show the full path but offer little detail about ownership. Combine test evidence with runtime observation and code navigation.

  1. Find the narrowest test related to the chosen behaviour.
  2. Run it and confirm the result is reproducible.
  3. Trace the fixture and assertion to the production path.
  4. Identify the boundary the test replaces or ignores.
  5. Add the finding to the map with its limitation.

Ask people for exceptions, not a complete tour

A maintainer interview is most useful after you have a tentative map. Instead of asking “How does this system work?”, ask which part of your trace is misleading, which failure has surprised the team and which module has an owner outside the repository.

People remember exceptions and history that code cannot express: a compatibility rule for one customer, a job that must run after a manual step, or a table shared with an old service. Record the claim and seek a traceable artifact where possible. Do not turn an informal memory into an unquestioned system fact.

Inès, Bas and Ravi trace a software request through code, data and integrations on separate screens.
A useful walkthrough challenges a concrete evidence map and exposes the exceptions that source reading misses.

Make one safe change to test the map

Understanding becomes more reliable when it predicts the effect of a controlled change. Choose a reversible, low-risk modification or add a characterization test around the current behaviour. Predict which files, checks and outputs will change before editing.

If the prediction is wrong, update the map. The mismatch is valuable evidence about hidden coupling. Keep the first change narrow; the purpose is to test your model, not to begin a broad cleanup before you understand the consequences.

For production systems, avoid experimenting with real data or unreviewed migrations. Use a development copy, fixture or read-only trace. Escalate when the only available proof crosses a sensitive boundary.

Turn the map into living documentation

Keep the useful path, commands and ownership notes in the repository. Add links to operational and business sources instead of copying them. The article on where developer documentation should live gives a location rule for each type of knowledge.

The map should state its scope and evidence date. It is a starting point for a feature area, not a claim to describe the entire system. Connect updates to code changes so the next developer can review the map alongside the behaviour it explains.

A codebase becomes less mysterious one verified path at a time. The goal is enough understanding to make the next decision safely, with visible limits around what remains unknown.

If your team needs to change a legacy system but cannot establish how it behaves, Build with us. Eliyce agrees the scope and evidence in writing and delivers reviewable software; the page explains the working relationship without promising a specific outcome.

Frequently Asked Questions

How do you understand an undocumented codebase without getting lost?

Choose one important behaviour, run it, trace its entry point, decisions, data and effects, and record evidence as you go. Stop when you can explain and safely test that slice. Expand only when the next task needs another boundary.

Should you read every source file in an unfamiliar codebase?

No. Sequential reading gives detail without priority. Start from a runtime behaviour, symbol, route or job, then follow definitions and references that affect it. Read surrounding files when the trace shows they matter.

Does drawing a UML diagram help with an undocumented system?

A diagram can help if it records verified relationships and a clear scope. It becomes harmful when guesses look authoritative. Use simple boxes and arrows, label confidence and link important connections to code, commands or observations.

What if the undocumented application does not run locally?

Record the exact failure and inspect build, environment and deployment files to identify the missing condition. Seek a safe existing environment or owner. Treat reproducible setup as a separate task instead of quietly changing dependencies during exploration.

How do you find hidden business rules in legacy code?

Trace validations, state transitions, data queries, error messages and tests around a real behaviour. Ask maintainers about exceptions and incidents, then seek supporting tickets, runbooks or production observations. Keep unverified memories labelled as such.

When do you understand enough to make a change?

You understand enough when you can predict the affected behaviour and boundaries, name the likely failure, choose a safe verification method and identify who should review the risk. You do not need a complete model of the entire codebase.

Written by

Haider Lasani

Has built and maintained real systems since 2008 and writes for engineering leaders whose teams ship with AI. Author of The Decisions AI Can’t Make for You and The Backlog That Thinks.

About the author

Recognise this in your own team?

Eliyce helps in three ways: your engineers deliver with AI inside one enforced process in the AI Delivery Programme, your business agents are built and managed like real software, and Spine shows what shipped and how it was checked.