How to Perform Large Code Refactors in Cursor

Editor
16 Min Read


has historically been a tedious yet important task. Refactoring is the work of taking some piece of code and cleaning it up, either by better separation of concerns, the Don’t Repeat Yourself (DRY) principle, or other code hygiene principles.

Code refactors have always been important, but with the release of coding agents, we’re seeing higher coding outputs, which inevitably leads to more need for code refactoring. I more and more often find myself in situations where some code needs to be refactored, though I don’t think this is a warning sign, considering the amount of code I output is also significantly higher now with the help of LLMs.

Luckily, the effort to refactor code has significantly gone down since the release of LLMs.

In this article, I’ll go through my high-level approach to performing code refactoring using coding agents like Cursor or Claude Code. I’ll cover my generic approach and thought process, so the model you utilize doesn’t matter.

This infographic covers my high-level approach to refactoring code using LLMs. I first discovered when to refactor, usually triggered by detecting a lot of anti-patterns, and noticing that implementing code is now slower. I then start planning the refactoring, using Gemini, and the plan mode in Cursor. Moving on, I execute the refactoring, where I prefer the Claude models, and I’m liberal with my whitelisted commands. I then verify the refactor by asking my coding agent to compare against the main branch and performing a secondary AI review with a refreshed context. Image by Gemini.

Why perform code refactoring

You should perform code refactoring whenever you notice a lot of antipatterns in your code, or when you notice you (or your coding agent) is spending more time than should be needed on an implementation. Your threshold before performing a refactoring should also be lower than it was before the release of LLMs, considering refactors are way easier and faster to implement now using coding agents.

This is because part of the training set for coding agents is to refactor code, and they’re especially good at that. In a lot of cases, I would even say they’re better than humans, considering refactoring requires a lot of working memory:

  • Remembering all variables
  • Ensuring input/output after the refactor is the same as before the refactor
  • Which files to move, delete, and add

Thus, you should perform code refactoring when:

  • You or your coding agent discovers a lot of antipatterns in your code
  • Implementations start taking longer than they should (a sign of bad code)

And you should perform code refactorings because:

  • They increase iteration speed
  • They are relatively cheap to perform with LLMs

My approach to refactoring code

In this section, I’ll cover my high-level approach to refactoring code. I’ll go through four steps:

  1. Discovering when to refactor
  2. What to consider before the refactor
  3. What to consider during the refactor
  4. What to consider after the refactor

This will highlight how you can approach refactoring yourself, in such generic terms that you can replicate the processing yourself on your own codebase.

1. Discovering when to refactor

The first step is always to discover when you should decide to refactor your code. There often isn’t a clear line on when refactoring should be performed or not, and it thus requires some instinct to know when is a good time.

However, you should apply some simple generic principles. If you see a lot of antipatterns in the code, for example, with:

  • A lot of duplicate code
  • Missing docstrings and function types
  • Poor separation of concerns

You should consider refactoring.

Also, if you notice your coding agent is spending longer than usual reading through your codebase, trying to understand it. Or it struggles more often with implementing something, and errors elsewhere pop up. You should also consider refactoring.

However, picking up this instinct takes time, and in the beginning, you can attempt to refactor earlier rather than later (considering that performing a refactor is cheap with LLMs), and then adjust when you learn more along the way.

2. What to consider before the refactor

When you’ve come to this step, you have decided that a certain part of a code repository should be refactored. Now you need to plan for which scope the refactoring should cover, since you should naturally reduce the refactoring scope as much as possible.

Reduce the scope of the refactoring as much as possible

I then start planning, which I do in mostly two ways:

  1. (optional) If I want to discuss generic architectural decision-making or high-level ideas, I start chatting with Gemini in the console. I explain my situation, the trade-offs, the different solutions I’m considering, and all other relevant information. I then have a conversation with Gemini about the decision. Notice the word conversation. I’m not simply asking Gemini a question about how to solve my problem; I’m discussing with the model what could be the best approach, and trying to understand the issue as best as possible.
  2. I always start off using plan mode in either Cursor or Claude Code, where you tell the model what you want to do, it looks through your code base, and it comes up with a plan for how to implement a solution into your code base. Sometimes, I copy over my conversation from Gemini (if I did that), and sometimes I simply start my refactoring directly in Cursor.

Planning your approach is super valuable, since you become aware of some issues you weren’t aware of before, and can make decisions with as much information as possible. Furthermore, you can read through the plan that Cursor makes, and tweak it if need be. Most importantly, though, I recommend having a conversation with your coding agent about how to approach refactoring it, as opposed to just asking it a simple question and only getting one response.

You should strive to have conversations with your agents, and not only a single question and response

I recommend spending at least 10-15 minutes on this conversation when performing a significant refactoring.

After a plan is made, I move on to step 3.

"""
Example plan.md file after using plan mode
In this scenario, the context is refactoring a messy, monolithic `server.js` (Express node app) into a cleaner MVC (Model-View-Controller) architecture with TypeScript.
"""
***

# Plan: Refactor Monolithic Server to MVC Architecture

## Context
Currently, `src/server.js` contains all database connections, route definitions, and business logic in a single file. We need to refactor this into a modular structure using TypeScript, splitting concerns into Controllers, Services, and Routes.

## User Requirements
1.  Convert the project to **TypeScript**.
2.  Extract database logic into a Singleton/Service.
3.  Separate routes into `src/routes`.
4.  Move business logic to `src/controllers`.

## Proposed File Structure
* `src/app.ts` (Entry point)
* `src/config/database.ts` (DB connection)
* `src/routes/userRoutes.ts` (Route definitions)
* `src/controllers/userController.ts` (Request handling)
* `src/services/userService.ts` (Business logic)

---

## Step-by-Step Plan

### Phase 1: Setup & Configuration
- [ ] Initialize TypeScript configuration (`tsconfig.json`).
- [ ] Install necessary `@types` dev dependencies (`node`, `express`).
- [ ] Rename `server.js` to `server.ts` temporarily to resolve immediate linting errors.

### Phase 2: Database Layer
- [ ] Create `src/config/database.ts`.
- [ ] Move the MongoDB connection string and connection logic from `server.ts` to `src/config/database.ts`.
- [ ] Ensure the database connection exports a robust singleton or connection function.

### Phase 3: Service & Controller Extraction
- [ ] Create `src/services/userService.ts`.
    - [ ] Move raw database queries (find, create, update) here.
    - [ ] Define interfaces for User data.
- [ ] Create `src/controllers/userController.ts`.
    - [ ] Implement `getUsers`, `createUser`, and `updateUser` methods.
    - [ ] Import `userService` to handle the logic.
    - [ ] Ensure proper type typing for `Request` and `Response`.

### Phase 4: Routing
- [ ] Create `src/routes/userRoutes.ts`.
    - [ ] Setup `express.Router()`.
    - [ ] Map endpoints (`/users`, `/users/:id`) to the specific Controller methods.

### Phase 5: Entry Point Cleanup
- [ ] Create `src/app.ts`.
    - [ ] Initialize the Express app.
    - [ ] Import and apply middleware (CORS, JSON body parser).
    - [ ] Mount `userRoutes`.
    - [ ] Connect to the database using `src/config/database.ts`.
- [ ] Delete the original `src/server.js`.

### Phase 6: Verification
- [ ] specific verification step: Start the server using `ts-node src/app.ts` to ensure no runtime errors.
- [ ] specific verification step: Test the `/users` endpoint to ensure data flow is intact.

---

### Would you like me to create this file structure for you now, or start specifically with Phase 1?

3. What to consider during the refactor

If you’ve come to this step, you’ve already prepared a good plan with your coding agent, and you’ve started performing the refactor. To make the refactor efficient and to ensure it works as well as possible, I keep these things in mind:

  • Be as lenient as you can with permissions. Of course, be careful with destructive commands, but having the agent be able to perform most commands significantly speeds up the process
  • Using Claude. My experience is that Claude Sonnet/Opus 4.5 are significantly the best and fastest coding model out there
  • Tell the agent to create testing scripts if needed. Sometimes the agents just try to debug from the code alone, but often it’s better to inform it to create a testing script where it can see the input and output

I then let the Coding agent run until it’s done, which can take anywhere from a minute to 20 minutes. If it’s one of the first times you’re running code in the repo, you might have to allow list some commands, but as mentioned, I try to be lenient here, especially when we’re talking about read commands, which can’t cause any damage.

I also allow my agent to create test scripts and run them at will, to not only debug the code from looking at the code, but also from seeing input and output.

With this setup, I’m mostly succeeding with the refactor with a maximum of a few prompts back and forth, and in many cases just one prompt.

4. What to consider after the refactor

After the refactoring is done, you need to consider the changes. Sometimes you should look through all the code thoroughly, though sometimes it’s not necessary. However, I have some particular recommendations after a refactor is done:

  • Ask the model to compare input and output before the refactor (point the model to your main or dev branch, whatever branch you branch off of when starting a new branch). The model will provide you with an overview, and you should usually ensure that the input and output are the same before and after. This tip has saved me a lot of bugs
  • Run a coding review with a separate agent (start it with a new context), which makes it easier to point out errors your agent didn’t see when refactoring
  • Ask Cursor to provide a thoughtful commit message, and create the PR for you. This both speeds up the process of getting the code to production, and it makes your PR’s more descriptive

Particularly, my first point on comparing against the main/dev branch is important. Having the model compare the input and output with the previous code and the current code has uncovered so many bugs that the model didn’t see during refactoring.

I believe that with even better coding models, we’ll see fewer and fewer of these mistakes, though for now, I definitely think it’s valuable to have the model to a second review of the changes, both through comparing input and output, and also by conducting a code review.

Conclusion

In this article, I’ve provided a high-level overview of how I perform code refactors. I first discussed how to know when a refactor is needed, before diving into the specifics of the refactoring approach. I then covered how I use plan mode before the refactoring, allow list commands during the refactoring, and ask the model to do a second review of the refactored code, with a refreshed context window.

I believe LLM refactoring will become more and more important as we see higher and higher coding output, because of LLM coding agents. I also believe that refactoring with LLMs is super effective, and something everyone should keep in mind, especially when coding a lot using coding agents.

👉 My free eBook and Webinar:

🚀 10x Your Engineering with LLMs (Free 3-Day Email Course)

📚 Get my free Vision Language Models ebook

💻 My webinar on Vision Language Models

👉 Find me on socials:

💌 Substack

🔗 LinkedIn

🐦 X / Twitter

Share this Article
Please enter CoinGecko Free Api Key to get this plugin works.