Refactoring Large Files with Claude Code (Without Blowing the Context Window)

Refactoring Large Files with Claude Code (Without Blowing the Context Window)
A 600-line React component. A 1,200-line service class. A 2,000-line utility module that "just grew over time." Every codebase has them, and every developer eventually needs to break them apart.
Claude Code is excellent at refactoring — in theory. In practice, most large-file refactors fail because of a math problem: a 600-line file consumes roughly 18,000-24,000 tokens when read into context. Add its imports (8,000-15,000 tokens), the files that depend on it (10,000-20,000 tokens), and the output Claude Code needs to generate (15,000-30,000 tokens), and you've exceeded a 100K token context window before Claude Code even starts thinking about the refactoring strategy.
The result: truncated output, missed dependencies, and half-finished refactors that leave your codebase in a worse state than before.
Here's the approach that actually works.
Why Naive Refactoring Fails
The instinct is reasonable: "Claude Code, refactor `src/services/UserService.ts` into smaller modules." Claude Code reads the file, reads its imports, reads all callers, formulates a plan, and generates the refactored code. Simple.
Except the token math doesn't add up.
Consider a real example — a 800-line `UserService.ts` that handles authentication, profile management, notification preferences, and session tracking. Here's the context budget breakdown:
- Source file: 800 lines → ~24,000 tokens
- Direct imports (5 files): ~12,000 tokens
- Files importing UserService (11 files): ~28,000 tokens
- Claude Code's refactoring output (8 new files + import updates): ~25,000 tokens
- Reasoning overhead (internal chain-of-thought): ~15,000 tokens
Total: ~104,000 tokens. That's the entire context window consumed by a single refactoring operation. There's no room for error correction, follow-up questions, or the inevitable "actually, I also need to update the test files."
The typical failure modes:
- Truncated output — Claude Code generates 4 of 8 planned files before running out of output tokens
- Missed callers — The 11th file that imports `UserService` gets skipped because there wasn't room to read it
- Broken imports — Callers reference the old module path because Claude Code couldn't hold all the update targets in context simultaneously
- Lost logic — Subtle behavior in the original file gets dropped during extraction because the context window couldn't hold both the original and refactored versions
The Scoped Refactoring Approach
The fix is decomposition. Instead of one massive refactoring operation, break the work into 3-4 focused phases, each scoped to fit comfortably within the context window.
This approach treats context like a budget. Each phase gets enough context to do its job well, with headroom for reasoning and output. No phase tries to hold the entire refactoring scope at once.
Phase 1: Analyze Dependencies
Before changing anything, understand the dependency graph. This phase is read-only — no edits, no risk.
Prompt:
```
Analyze src/services/UserService.ts. List:
- Every function/method exported from this file
- Every file that imports from it, and which exports each file uses
- Logical groupings (which functions belong together based on their purpose)
Suggest a refactoring plan that splits this into smaller modules.
```
Context cost: ~40,000 tokens (source file + imports + callers + reasoning). Well within budget.
Output: A structured plan. Something like:
- `src/services/auth/` — `login()`, `logout()`, `validateSession()`, `refreshToken()`
- `src/services/profile/` — `getProfile()`, `updateProfile()`, `uploadAvatar()`
- `src/services/notifications/` — `getPreferences()`, `updatePreferences()`, `subscribeToChannel()`
- `src/services/sessions/` — `createSession()`, `destroySession()`, `listActiveSessions()`
This plan becomes the blueprint for the next phases. Save it — you'll reference it in every subsequent prompt.
Phase 2: Extract One Unit at a Time
Now execute the plan one logical unit at a time. Each extraction is a small, focused operation.
Prompt (first extraction):
```
Extract the authentication functions from src/services/UserService.ts
into a new module at src/services/auth/authService.ts.
Functions to extract: login(), logout(), validateSession(), refreshToken()
These functions are imported by:
- src/routes/auth.ts (uses login, logout)
- src/middleware/authGuard.ts (uses validateSession)
- src/jobs/tokenRefresh.ts (uses refreshToken)
Update all import statements in the callers.
Keep the original functions in UserService.ts for now — just add
re-exports that point to the new module for backward compatibility.
```
Context cost: ~25,000 tokens. The source file, the 3 caller files, and the new output file. Plenty of headroom.
Why re-exports? They provide backward compatibility during the refactoring process. Other files that import from `UserService` still work while you extract units one by one. You remove the re-exports in a final cleanup phase.
Repeat this prompt pattern for each logical unit: profile functions, notification functions, session functions. Each extraction is its own Claude Code interaction — use `/clear` between extractions to keep the context window clean.
Phase 3: Update Callers
After all units are extracted, update the remaining callers to import from the new module paths instead of going through `UserService` re-exports.
Prompt:
```
Update these files to import directly from the new auth service
module instead of from UserService:
- src/routes/auth.ts: change import { login, logout } from '../services/UserService'
to import { login, logout } from '../services/auth/authService'
- src/middleware/authGuard.ts: same pattern
- src/jobs/tokenRefresh.ts: same pattern
Then remove the re-exports for auth functions from UserService.ts.
```
Context cost: ~15,000 tokens. Each update is mechanical — Claude Code just needs to see the import statements and change the paths.
Repeat for each extracted module's callers.
Phase 4: Clean Up
Final phase: remove the now-empty `UserService.ts` (or keep it as a slim facade if some functions remain), update barrel exports, and run the test suite.
```
Remove src/services/UserService.ts now that all functions have been
extracted. Update src/services/index.ts to export from the new module
paths. Run the full test suite and fix any broken imports.
```
Context cost: ~10,000 tokens. Mostly mechanical import path updates.
How a Dependency Graph Changes Everything
The scoped refactoring approach works well when you manually identify dependencies. But for large files with deep dependency chains, manually mapping "what depends on this function" across the entire codebase is tedious and error-prone.
This is where a tool like vexp pays for itself. Its impact graph takes a symbol (function, class, export) and traces every file that depends on it — directly or transitively. Instead of manually searching for "which files import `validateSession`," the graph returns the complete dependency chain in milliseconds.
Manual dependency mapping:
- Read the source file
- Search for import statements referencing it across the codebase
- For each caller, check if it re-exports the symbol (transitive dependencies)
- Build a mental map of the dependency tree
- Time: 10-20 minutes. Token cost: 15,000-30,000 tokens.
Dependency graph query:
- One `run_pipeline` call with the refactoring task
- Returns all affected files, ranked by impact, with the specific symbols each file uses
- Time: 5 seconds. Token cost: 300-600 tokens.
The graph also reveals dependencies you'd miss manually. Transitive imports, dynamic requires, re-exports through barrel files, and framework-level dependency injection patterns all show up in the graph but are easy to overlook when searching manually.
For the `UserService` example, vexp's impact graph would show that `validateSession` is imported directly by 3 files — but transitively affects 9 files through the auth middleware that gates 6 route handlers. Missing those transitive dependencies during refactoring means broken routes in production.
Practical Walkthrough: Refactoring a 500-Line Component
Let's apply the full workflow to a real scenario. You have `src/components/Dashboard.tsx` — a 500-line React component that renders stats, charts, recent activity, and user settings.
Step 1: Clear context and analyze.
```
Analyze src/components/Dashboard.tsx. List every component,
hook, and utility function defined in this file. Identify logical
groupings and suggest how to split this into focused components.
```
Claude Code produces a plan:
- `DashboardStats` — KPI cards and metrics
- `DashboardCharts` — chart components with data fetching
- `ActivityFeed` — recent activity list
- `DashboardSettings` — user preference toggles
- `useDashboardData` — shared data fetching hook
Step 2: Extract one component at a time. Start with the simplest extraction — the one with the fewest dependencies on the parent component's state.
```
Extract the DashboardStats section from Dashboard.tsx
(lines 45-120) into src/components/dashboard/DashboardStats.tsx.
It uses these props from the parent:
- stats: DashboardStats (type from src/types/dashboard.ts)
- timeRange: 'day' | 'week' | 'month'
Create the component with those props and update Dashboard.tsx
to import and render DashboardStats instead of the inline JSX.
```
Context cost: ~12,000 tokens. One source file, one new file, one type import.
Step 3: Clear context, repeat for the next component. `/clear` between extractions keeps context lean.
Step 4: After all extractions, run tests.
```
Run the test suite for Dashboard and all new extracted components.
Fix any broken imports or missing props.
```
Total token cost across all phases: ~50,000-65,000 tokens. Compare that to a single-shot refactoring attempt at 104,000+ tokens that fails due to context overflow.
Context Budget Management During Refactoring
Track your context usage across refactoring phases. Here's a budget template:
| Phase | Files in context | Estimated tokens | Headroom |
|-------|-----------------|-----------------|----------|
| Analysis | Source + imports + callers | 35,000-45,000 | 55,000+ |
| Extract unit 1 | Source + unit callers + new file | 20,000-30,000 | 70,000+ |
| Extract unit 2 | Source + unit callers + new file | 20,000-30,000 | 70,000+ |
| Update callers | Caller files only | 10,000-20,000 | 80,000+ |
| Cleanup + tests | New modules + test output | 15,000-25,000 | 75,000+ |
The key insight: no single phase exceeds 50% of the context window. This leaves ample room for Claude Code's reasoning, output generation, and any follow-up corrections.
Three rules for staying within budget:
- One extraction per Claude Code interaction. Use `/clear` between extractions. Don't try to extract all units in a single session.
- Include only the files Claude Code needs for the current phase. During caller updates, don't include the full source file — just the callers and the new import paths.
- Use a context engine for dependency discovery. Let vexp's graph identify affected files instead of reading them all into context manually. The graph data is compressed — file paths and symbol names, not full source code — so it costs 10x fewer tokens than reading the files directly.
When Not to Use This Approach
Scoped refactoring isn't always the right call:
- Files under 200 lines — Small enough to refactor in a single shot. No phasing needed.
- Pure rename refactors — If you're just renaming a function or moving a file without restructuring, use your IDE's built-in rename. It's faster and more reliable than Claude Code for mechanical transformations.
- Tightly coupled code — If every function in the large file shares state with every other function, extraction won't clean things up. You need to refactor the state management first, which is a different kind of task.
- No tests — Refactoring without tests is risky regardless of approach. Write tests first (Claude Code is excellent at this), then refactor.
For everything else — the 400-2000 line files that accumulate in every mature codebase — the phased approach transforms an impossible context-window problem into a series of manageable, verifiable steps.
Frequently Asked Questions
How do I refactor large files with Claude Code without running out of context?
What's the maximum file size Claude Code can refactor effectively?
Should I use Claude Code Opus or Sonnet for refactoring?
How does vexp help with refactoring in Claude Code?
What if my refactoring breaks something — how do I recover?
Nicola
Developer and creator of vexp — a context engine for AI coding agents. I build tools that make AI coding assistants faster, cheaper, and actually useful on real codebases.
Related Articles

Vibe Coding Is Fun Until the Bill Arrives: Token Optimization Guide
Vibe coding with AI is addictive but expensive. Freestyle prompting without context management burns tokens 3-5x faster than structured workflows.

Windsurf Credits Running Out? How to Use Fewer Tokens Per Task
Windsurf credits deplete fast because the AI processes too much irrelevant context. Reduce what it needs to read and your credits last 2-3x longer.

Best AI Coding Tool for Startups: Balancing Cost, Speed, and Quality
Startups need speed and budget control. The ideal AI coding stack combines a free/cheap agent with context optimization — here's how to set it up.