
How to understand legacy code comes down to order. Start with what the business uses the system for and what goes in and out, get a copy running, and trace one real transaction end to end. Then read the schema, the dependencies and the history, and pin current behavior with tests before you change anything.
This is the order we follow when we open a codebase for the first time: ten steps, a table of what to record, what reverse engineering means, and where AI helps.
- Read a legacy codebase from the outside in: what the business uses it for, who uses it, what goes in and out, and what data it keeps.
- Get a copy running, then trace one real transaction end to end. One complete path teaches more than a week of browsing files.
- The database schema shows what the system believes about the business. Version control history shows why the code changed.
- Pin current behavior with characterization tests before you change anything, and write down what you learn as you go.
How to understand legacy code, step by step
Steps 9 and 10 run alongside the others.
1. Start outside the code
Before opening a file, ask the business owner what the system decides and what would stop without it. Find out who uses it, what goes in (files, feeds, API calls) and what comes out (reports, exports, invoices). An hour watching someone use it gives every later step a reason.
2. Get it running
Get a copy you're allowed to break, with realistic data. Write down every step the setup guide missed; that list is your first piece of documentation. You can read code you can't run, but you can't confirm anything you read.
3. Find the entry points
List every place work comes in: web routes, screen definitions, scheduled jobs, message listeners, scripts and database triggers. Check the job scheduler on the server, not only the repository. An entry point nobody can explain is a finding.
4. Trace one real transaction end to end
Pick something the business does daily, such as an order, and follow it through: where it starts, the validation, the calculations, every table it writes, the overnight job, and the report where it ends. Use a debugger or extra logging, and record each hop with file, line and table. Then trace a failure path, because exception handling is where old systems keep their surprises.
5. Read the database schema
The schema shows what the system believes about the business: what an account is, which states an order can be in. Read the keys and constraints, then the stored procedures, triggers and views, which often hold logic the application doesn't show. Then look at the data, because column names and contents drift apart. Note every table more than one program writes to.
6. Map the dependencies
Inside the code, note which modules call which. Outside it, list file drops, queues, database links and reports run straight against production; connection strings and file paths in configuration are the quickest way in. For a system-level pass, use our software architecture review checklist.
7. Use version control history to learn why
The code shows what; the history often shows why. Git's documentation says git blame "Annotates each line in the given file with information from the revision which last modified the line." git log with -L traces how a function or range of lines changed, and --follow keeps a file's history going past renames. Read the commit messages and the tickets they mention. Files that change often and keep turning up in bug fixes deserve the closest reading.
8. Pin current behavior with tests
Before changing anything, write tests that record what the code does today, right or wrong: realistic inputs, captured outputs, assertions. Michael Feathers calls these characterization tests. When one pins behavior that looks like a bug, label it and ask the business owner, because something downstream may rely on it.
9. Write it down as you go
Keep notes in the repository, next to the code, and mark what you confirmed and what you inferred.
10. Talk to the people who run it
Operators, support staff and long-serving developers know what isn't in the code: which job fails every quarter-end, which report finance fixes by hand. Bring specific questions from your reading. If one person holds most of that knowledge, start capturing it now.
What to answer, where to look, what to record
| Question you need answered | Where to look | What to record |
|---|---|---|
| What does the business use it for? | Business owner, users, the reports people rely on | One-sentence purpose, user groups, key outputs |
| How do I run it? | Build scripts, setup guide, a working developer's machine | Steps that worked, steps that were missing |
| Where does work come in? | Routes, screens, job schedules, listeners, triggers | Every entry point and what starts it |
| What happens to one real transaction? | Debugger, logs, code path, tables written | Each hop, with file, line and table |
| What does it believe about the business? | Schema, constraints, reference tables, the data | Glossary of tables, codes and odd values |
| What does it depend on, and what depends on it? | Includes, connection strings, file paths, job chains | A rough dependency map |
| Why is this code here? | git blame, git log -L, commit messages, tickets | The reason, or "unknown" |
| What does it do today, exactly? | Characterization tests | Pinned behavior, suspected bugs labeled |
| Who knows the rest? | Operators, support staff, long-serving developers | Names, and the questions only they can answer |
What is reverse engineering legacy code?
Reverse engineering legacy code means recovering a system's design and business rules from the code itself, because the documents that should describe them are missing or wrong. The output is a component diagram, a data-flow map, a list of interfaces and the rules the system enforces.
Take it as far as the decision in front of you. A bug fix needs the path you'll touch. An upgrade needs the dependencies and interfaces. A rewrite needs every rule, because the replacement must reproduce them. Recovering rules is a discipline of its own, covered in business rule extraction.
How to document legacy code
Document what the next person needs to change the system safely, not every function: its purpose and who relies on it, how to build and run it, the entry points and dependency map, a glossary of terms and status codes, the confirmed rules with their source locations, and the reason behind each strange part, or "reason unknown." Date every page and say where each fact came from.
How to use AI to understand legacy code
AI assistants are good at local work: explaining a function, summarizing a module, finding usages, drafting a test. They see only what's in front of them and can explain code fluently and wrongly, so treat each explanation as a hypothesis and check it against the running code. More in AI for legacy code.
When an outside read is worth it
This method takes weeks of a senior developer's time, and that developer is usually also keeping the system running. That's where the 30-day assessment fits. It's fixed in scope, at a price agreed before we start. Over 30 days we read the codebase and the architecture around it, trace the transactions that matter, map how the system fits together, find the hidden architectural debt, and flag the security and compliance gaps. Much of what we check is on our code audit checklist.
The report says in plain language what is broken and what it would take to fix it, and doubles as the map your team didn't have. Your own team can act on it, or we can do the work as a separate project with its own scope and price. The report stands on its own either way.
Send one sentence about the system and what it runs on, and we'll tell you whether the assessment is the right next step.