I recently moved from data science into AI engineering. I can write code, but I have less traditional software-engineering experience, so I often rely on AI to implement features and explain unfamiliar parts of a project. I can follow individual lines, yet I struggle to retain the larger picture: the architecture, data flow, dependencies, and reasons behind design decisions. A coworker suggested that I should study the code myself before asking AI for help. What routines or exercises have helped you become better at reading code, reviewing pull requests, tracing execution, and building a lasting mental model of an unfamiliar system? What is your usual process when joining a new codebase?
5 Answers
Pick one real behavior that the application already performs, such as handling a request or processing a record. Explain what you think should happen, then find the code responsible—sometimes starting at the final output and working backward is easiest. At each step, write down the conditions and assumptions, read upstream code to verify them, and explain any surprises. Once you understand one behavior end to end, repeat the exercise with another. Making a small, reversible change and predicting its effect is also a great way to test your mental model.
Learn the framework and major libraries before trying to understand every implementation detail. Identify the conventions and patterns repeated across the project, then use those to interpret individual modules. Tests, configuration, route definitions, and dependency setup are often more useful at the beginning than reading files from top to bottom. For unusual decisions, inspect the history: git blame can show when a line changed, while git log -p can reveal the context and rationale in the original commit.
Start with the system's entry points and follow one complete path to its exit. In a web service, that might mean choosing one route or API endpoint and tracing the request through validation, business logic, storage, and the response. Skim broadly first to locate the important areas, then read the relevant sections carefully or step through them in a debugger. Treat each endpoint or job like a small main method, and gradually connect those paths into a larger architecture.
The difficult part for me is recognizing the entry and exit points without asking an AI first, but starting with routes and other obvious boundaries gives me something concrete to investigate.
Use AI as a guide rather than a replacement for exploration. Before asking for an explanation, make your own diagram or prediction of the flow, then ask targeted questions about specific files, symbols, or assumptions. Have it point out likely entry points, summarize a module, or suggest where a behavior is implemented, but verify everything by reading the code, running tests, and tracing an execution path yourself. Keeping a short architecture note or flow diagram that you update as you learn will help the model stick over time.
Debugging turns static code into something much easier to follow. Put a breakpoint in an important function, inspect the values, use the call stack to move upward to its callers, and step into functions to trace dependencies downward. Choose a bug or a small feature as your investigation target rather than trying to read the whole repository. Tests and a debugger let you check whether your explanation matches what the program actually does.

Explaining the behavior out loud or to a rubber duck helps expose the gaps in my understanding. The goal isn't memorizing files; it's being able to explain why data moves through each stage.