Lesson 9 of 20

Lesson 08: Working with Large Codebases

The Challenge

Claude has a context window limit. A large codebase won't fit. The key skill is helping Claude navigate to exactly the right information without overwhelming the context with irrelevant files.


How Claude Navigates Code

Claude uses several tools to explore your codebase:

Tool Use Case
Glob Find files by pattern: **/*.ts, src/api/**
Grep Search file contents: find all uses of a function
Read Read a specific file (or portion of it)
Task (Explore agent) Deep multi-file research

The better your CLAUDE.md describes the project structure, the less exploring Claude has to do.


Strategy 1: Give Claude the Map

The single best thing you can do for large codebase work is describe the architecture in CLAUDE.md:

## Project Structure
- /src/api — HTTP route handlers (thin layer, no business logic)
- /src/services — Business logic (all domain operations live here)
- /src/db — Database access via Prisma (all queries here, nowhere else)
- /src/lib — Shared utilities (logging, HTTP client, config)
- /src/types — TypeScript interfaces (no implementation)
- /tests — Mirror of /src structure

## Key Entry Points
- API server: /src/server.ts
- Background workers: /src/workers/index.ts
- DB schema: /prisma/schema.prisma
- Environment config: /src/lib/config.ts (reads from .env)

## Important Patterns
- All service methods return `Result<T, AppError>` (never throw)
- Events use the event bus in /src/lib/events.ts
- Auth context is always passed as first argument to service methods

With this map, Claude can go directly to the right files instead of grep-crawling the entire repo.


Strategy 2: Point Claude to the Right Place

Instead of "add user authentication," say:

"Add a deactivateAccount(userId) method to /src/services/user.service.ts. It should set status: 'deactivated' and emit a user.deactivated event via the event bus at /src/lib/events.ts. Follow the pattern used by deleteAccount() in the same file."

You're telling Claude:

  • Exactly which file to work in
  • Exactly what existing pattern to follow
  • Exactly what external systems to use

Strategy 3: Use Sub-Agents for Exploration

For genuine exploration tasks (finding all places a concept appears, understanding an unfamiliar subsystem), ask Claude to use a sub-agent:

"Use the Explore agent to find all places where we handle payment webhook events. Give me a map of which files handle which events before we make any changes."

This keeps the exploration out of your main conversation context.


Strategy 4: Work Incrementally

On large tasks, don't try to do everything in one shot. Break it up:

Bad: "Migrate the entire auth system from JWTs to session tokens."

Good:

  1. Session 1: "Map out all files that touch JWTs. Don't make any changes yet."
  2. Session 2: "Create the session store and session middleware. Tests only."
  3. Session 3: "Update the login endpoint to create sessions instead of JWTs."
  4. Session 4: "Update the auth middleware to validate sessions instead of JWTs."
  5. Session 5: "Remove the old JWT code and update tests."

Each session is focused and reviewable.


Strategy 5: Leverage Grep Before Asking

Before asking Claude to find something, you can do a quick Grep yourself and hand Claude the results:

"I ran grep and found that calculateTax() is called from these 5 files: [list]. Please update all call sites to use the new calculateTax(amount, region) signature."

This saves Claude the exploration step and focuses it on the actual work.


Strategy 6: Reference by Line Number

When Claude has read a file, reference line numbers to be precise:

"The issue is in the processPayment function starting at line 234. The problem is on line 251 where it doesn't handle the CARD_DECLINED error code."

Claude holds line numbers in context. This is much faster than describing the code textually.


Working with Monorepos

For monorepos, be explicit about which package you're working in:

## Monorepo Structure
This is a Turborepo monorepo.
- /apps/web — Next.js frontend
- /apps/api — Express API
- /packages/db — Shared Prisma client and types
- /packages/ui — Shared React components
- /packages/utils — Shared utilities

When working on a feature, always specify which package(s) are involved.
Run commands from the repo root: `turbo run test --filter=api`

Handling Long Files

For very long files (1000+ lines), Claude can read them in chunks. Help it navigate:

"Read lines 450-600 of services/billing.ts — that's where the subscription renewal logic lives."

Or:

"The InvoiceProcessor class is in services/billing.ts. Read just that class (it starts around line 450)."


The "Before You Start" Pattern

For any large task, establish shared context first:

"Before we start, read these three files: services/user.ts, types/user.ts, and db/users.ts. Tell me what you understand about how users are structured in this system. Then I'll explain what we need to change."

This ensures Claude has the right mental model before it writes a single line.


Practical Exercise

Take a feature in a large project you work on. Instead of just asking Claude to implement it:

  1. Write out which files are involved (even if you're guessing)
  2. Describe the pattern Claude should follow (point to an existing similar feature)
  3. Define the scope explicitly (what files are in-bounds and out-of-bounds)
  4. Ask Claude to confirm its understanding before writing code

Compare the quality of the result to your previous "just ask and see what happens" approach.