Opening every folder is a slow way to understand a codebase. The directory tree tells you where code lives; it rarely tells you which parts matter for the behaviour you are trying to change.
Start with one concrete action. For an API, choose a request with a small input and an observable result. Find its route, follow the call into the service layer, and identify where it reads or writes state.
Build a map of boundaries
Write down the entry point, validation, business decision, persistence and response. You are looking for responsibility changes rather than collecting every function name.
The FreightSense repository, for example, has an evaluation route that connects deterministic calculations, an LLM call and a database record. Reading that path explains more about the application than reading unrelated utilities alphabetically.
Next, follow one failure path. What happens if the external service times out? Does the request fail, use a fallback, or enqueue work? That path often reveals assumptions that the successful example keeps hidden.
Use tests and history selectively
Tests near the behaviour can show the expected contract, although a test may also encode an old assumption. Configuration reveals which dependencies and feature choices change the runtime. Git history helps when a seemingly strange branch of code has a reason that the current file no longer explains.
Keep a short list of unresolved questions: where an identifier comes from, whether a write is transactional, or which worker owns retries. Answer the ones relevant to your change first.
You do not need complete understanding before making progress. You need a reliable account of the path you are touching, its neighbours, and the evidence that a change preserves the intended behaviour.
Example repository: FreightSense.