12 Code Refactoring Techniques for Cleaner Code in 2026
Learn 12 proven code refactoring techniques, from red-green to abstraction, plus how to refactor safely at scale across large codebases.

Learn 12 proven code refactoring techniques, from red-green to abstraction, plus how to refactor safely at scale across large codebases.
Most refactoring consists of a small set of repeatable moves. You rename a variable so it says what it holds, pull a tangle of logic out of a long function and give it a name, or flatten a nest of if-statements so the happy path reads top to bottom. Each move is mechanical, your editor does half the work, and once you've seen the pattern, you can apply it on sight. The catalog of these moves is what most people mean by "code refactoring techniques," and the first half of this guide walks through it with real before-and-after code.
The moves were never the hard part, though. The judgment around them is what separates a clean refactor from an incident. You have to know which forty lines out of four million actually need the work, prove you changed the structure without changing the behavior, and roll the same fix across hundreds of repositories before the deprecated API you're replacing reaches end-of-life. That second half, what refactoring becomes once the codebase no longer fits in one person's head, is the part the usual listicles skip, and it's where this guide goes after the catalog.
Code refactoring is the disciplined process of restructuring existing code to improve its internal design without changing its external behavior. The code does the same thing after the refactor as before; what changes is how easy it is to read, test, and modify the next time someone touches it.
Martin Fowler, who literally wrote the book on it, defines the heart of refactoring as "a series of small behavior-preserving transformations." Each step is tiny and low-risk, and the system keeps working after every one, which is what separates refactoring from a risky rewrite. Fowler is also explicit about what refactoring is not: a synonym for any old code cleanup. It's a specific technique with specific named moves, and that shared vocabulary is exactly what makes the rest of this guide useful in a code review.
The textbook answer is "continuously," and it's correct as far as it goes. Refactoring works best as a habit integrated into normal feature work rather than as a quarterly cleanup project. But "continuously" doesn't help you prioritize, and most teams operate under roadmap pressure where every hour spent restructuring code is an hour argued for. So here are the signals that a specific piece of code has earned attention now, ranked roughly by how defensible they are in a sprint-planning conversation.
Skip the refactor when the code is scheduled for retirement, when you can't test the result, or when the only justification is aesthetic preference. Refactoring without a safety net is where risk spikes. For legacy code, characterization tests are usually the first refactoring step: they pin down what the code currently does, quirks included, before you change its shape. We covered that workflow in depth in our legacy code modernization guide.
The canonical taxonomy is used by Refactoring.Guru's catalog, derived from Fowler's work, groups dozens of named refactorings into six categories. Knowing the categories matters more than memorizing every technique, because the category tells you what problem you're solving:
These first twelve techniques are presented in a single file; the section after them is about making those same techniques survivable across an organization. These twelve cover the bulk of day-to-day refactoring work. Most names follow the standard catalog, so your team can reference the full write-ups; two of them (red-green-refactor and refactoring by abstraction) are workflow-level patterns from the broader literature.
The workhorse. Pull a coherent fragment out of a long method and give it a name that says what it does.
# Before
def checkout(order):
total = 0
for item in order.items:
total += item.price * item.qty
if order.coupon and order.coupon.valid:
total -= total * order.coupon.rate
charge(order.customer, total)
# After
def checkout(order):
total = calculate_total(order)
charge(order.customer, total)
def calculate_total(order):
subtotal = sum(i.price * i.qty for i in order.items)
return apply_coupon(subtotal, order.coupon)
def apply_coupon(subtotal, coupon):
if coupon and coupon.valid:
return subtotal - subtotal * coupon.rate
return subtotal
The diff looks trivial. The payoff shows up the fifth time someone reads checkout() and doesn't have to re-derive the pricing rules.
The test-driven loop: write a failing test (red), write the minimum code to pass (green), then refactor with the test as your safety net. Even teams that don't practice strict TDD borrow the third beat. The test suite is what makes every other technique on this list safe to attempt.

The red-green-refactor loop: the passing test suite is what makes every other technique on this list safe to attempt.
The cheapest high-impact refactor in existence. process2() and data_final_v3 are messages from a past developer who gave up. Naming things for what they do removes a translation layer every future reader pays for.
The inverse of extract. When a method's body is as clear as its name, and the indirection adds nothing, fold it back in. Indirection has a cost, and a codebase of one-line pass-through methods is its own kind of smell.
Swap intermediate variables for well-named methods so that logic lives in one queryable place rather than being recomputed and cached ad hoc in a method body.
Flatten the arrow.
// Before
function payAmount(employee) {
let result
if (employee.isSeparated) {
result = { amount: 0 }
} else {
if (employee.isRetired) {
result = { amount: 0 }
} else {
result = computePay(employee)
}
}
return result
}
// After
function payAmount(employee) {
if (employee.isSeparated) return { amount: 0 }
if (employee.isRetired) return { amount: 0 }
return computePay(employee)
}
When the condition itself is the confusing part, extract it. A call to if (isEligibleForDiscount(order)) beats four chained boolean checks that the reader has to simulate mentally.
When the same switch on a type code appears in multiple methods, move each branch into a subclass or strategy. The switch disappears, and adding a new variant stops requiring archaeology across the codebase.
if (speed > 299792458) means nothing at review time. if (speed > SPEED_OF_LIGHT_M_PER_S) documents itself. This one matters most in domains where the constants encode business rules someone will need to change later.
When one class has two reasons to change, split it. The User class that handles authentication, profile data, notification preferences, and billing is four classes that should not really be positioned under the same umbrella.
Six positional parameters invite passing them in the wrong order. Group the ones that travel together into an object, and the signature becomes both shorter and harder to misuse.
For large-scale duplication across sibling classes, pull shared behavior up the hierarchy, or extract an interface and let implementations vary. This is the category to reach for during framework migrations, when you need old and new implementations to coexist behind one contract. The migration then becomes a sequence of safe swaps: introduce the abstraction, move callers over one at a time, and delete the old implementation when its reference count hits zero.
The code refactoring examples above are deliberately language-agnostic. The same moves work in Java, Python, TypeScript, or Go, and the standard catalogs document language-specific mechanics for each one.
Everything above assumes the code in question fits in your editor. The techniques don't change at enterprise scale, but the workflow around them changes completely, because the bottleneck moves from "how do I restructure this method" to "where are the other 4,000 places this pattern lives, and how do I change them all without a six-month migration."
This is the part most refactoring guides skip, and it's where teams actually lose quarters. A framework upgrade that takes an afternoon in one service becomes a cross-team program when the same pattern lives in every service, each with its own owners, review process, and deploy cadence. The three capabilities below are what turn that program back into a tractable engineering task.

IDE refactoring stops at the repo boundary; org-wide refactors need find, automate, and track.
Before you can apply technique #9 to a magic number or kill a deprecated client, you need an exhaustive list of every occurrence across every repo and branch. Grep on a local clone stops working when the code spans hundreds of repositories. Code search at the organization level solves the enumeration problem: regex, literal, and symbol queries across every repository in the organization, with cross-repository navigation that resolves where a symbol is defined and referenced. The point isn't speed for its own sake. It's that "every reference" stops being an estimate and becomes a list you can put in a tracking issue.
Applying the same mechanical refactor by hand across hundreds of repos is how migrations die. Batch Changes handles this with a single declarative spec that runs your transformation everywhere and tracks the resulting changesets through review and merge. Workiva's Joe Bingham put the value plainly in the team's case study: "Updating all of our repositories with Batch Changes saves time, is less error-prone, and gives us confidence that everything is going to plan." That's the at-scale version of Fowler's small-safe-steps principle: a single reviewed, reversible-change template instead of hundreds of hand-rolled variations.
Long-running refactors fail silently when nobody can see the trend. Code Insights turns a search query into a tracked metric, so "occurrences of @deprecated calls" or "files still on the legacy client" becomes a dashboard line that should only go down. The playbook is the same every time: enumerate, automate, then chart the burn-down until the old pattern reads zero.
The techniques fail without the discipline around them. The practices that consistently separate clean refactors from incident reports:
One pattern worth stealing from teams that do this well is to treat each named technique as a reviewable unit. A PR titled "Extract pricing calculation from checkout()" with no behavior change is approvable in minutes, because the reviewer knows exactly what to verify. A PR titled "cleanup" that mixes renames, logic changes, and formatting takes an hour and still ships bugs.
IDEs automate most single-file techniques. IntelliJ IDEA, Visual Studio Code, and PyCharm all handle the mechanical edits and most reference updates for extract, inline, and rename refactorings. In dynamic languages, you still verify with tests, since no tool can see every reflective or string-based reference. Fowler's take is worth keeping in mind here: automated tools are valuable, but not essential, and small steps plus frequent testing work in any language with or without tooling. For org-wide work, the tooling question becomes scale: codemod engines for deterministic transforms, and code intelligence platforms like Sourcegraph that combine search, automated changesets, and trend tracking across every repository at once.
The twelve techniques can be learned in a week. What separates teams that keep their codebase healthy from teams that schedule a "refactoring quarter" every two years is the surrounding system. Tests make changes safe, search makes scope knowable, automation makes org-wide changes routine, and metrics prove the work landed.
If your next refactor spans more repositories than you can clone locally, that's the problem Sourcegraph was built for. Find every instance, change them all, and watch the old pattern trend to zero.
What's the difference between refactoring and rewriting? Refactoring preserves external behavior through a sequence of small, reversible steps, with the system working throughout. A rewrite replaces the implementation wholesale and accepts a window where the new code doesn't yet do everything the old code did. Rewrites are sometimes right, but they need different risk planning, and calling a rewrite "refactoring" usually means the risk planning got skipped.
Does refactoring change behavior? By definition, no. If observable behavior changed, either a bug was fixed (a behavior change) or one was introduced (a regression). That definition is also the test for whether your refactor is done: the test suite that passed before should pass after, unchanged.
What are code refactoring patterns? In practice, "patterns" and "techniques" get used interchangeably for the named moves in the standard catalogs: Extract Method, Move Method, and the rest. Design patterns (factory, strategy, observer) are a different concept, though refactorings like Replace Conditional with Polymorphism often introduce a design pattern as their end state.
How do you practice code refactoring? Pick a real module with tests, take one smell, and apply one named technique end-to-end. Refactoring katas and the worked examples in the standard catalogs help, but the habit forms fastest when refactoring rides along with normal feature work: leave each file slightly better than you found it.

With Sourcegraph, the code understanding platform for enterprise.
Schedule a demo