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

Nicola·
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:

  1. Every function/method exported from this file
  2. Every file that imports from it, and which exports each file uses
  3. 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:

  1. One extraction per Claude Code interaction. Use `/clear` between extractions. Don't try to extract all units in a single session.
  2. 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.
  3. 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?
Break the refactoring into phases. Phase 1: analyze the file's dependencies and create a plan (read-only). Phase 2: extract one logical unit at a time into its own module, using /clear between extractions. Phase 3: update callers to import from new module paths. Phase 4: clean up and run tests. No single phase should exceed 50% of your context window. This approach reduces total token cost by 40-50% compared to attempting the entire refactor in one shot.
What's the maximum file size Claude Code can refactor effectively?
Claude Code can read files of any size, but effective refactoring depends on context window math. A 600-line file (~18K-24K tokens) plus its dependencies and output typically fits in a single shot. Files over 800 lines (~24K+ tokens) usually require the phased approach because dependencies and output push total context beyond the window limit. With a context engine to reduce exploration overhead, the effective limit increases by roughly 30-40%.
Should I use Claude Code Opus or Sonnet for refactoring?
Use Sonnet for the extraction phases — they're mechanical and well-scoped. Use Opus for the initial analysis phase where Claude Code needs to understand the file's structure, identify logical groupings, and produce a refactoring plan. The analysis phase benefits most from Opus's stronger reasoning. Switch back to Sonnet for caller updates and cleanup. This hybrid approach costs roughly 40% less than using Opus for everything.
How does vexp help with refactoring in Claude Code?
vexp's impact graph maps every file affected by changes to a specific symbol — directly and transitively. During refactoring, this eliminates the need to manually search for callers and dependents. One query returns all affected files, ranked by impact, with the specific symbols each file uses. This typically saves 15,000-30,000 tokens per refactoring session compared to manual dependency discovery, and catches transitive dependencies that manual searches miss.
What if my refactoring breaks something — how do I recover?
Always refactor on a git branch with frequent commits. Commit after each successful extraction phase so you can revert to the last working state. The phased approach makes recovery easier because each phase is independently verifiable — if phase 3 breaks callers, you revert to the post-phase-2 state and try again with better import mappings. Running tests after each phase catches issues early before they compound.

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