When to refactor (and when to leave it alone)

Status: 🟩 COMPLETE Last updated: 2026-06-21 Plain-English tagline: Refactoring is rearranging code without changing its behavior. Sometimes it pays for itself; sometimes it’s pure cost. This is the rule for telling them apart.


What this decides

You’re looking at code that works but feels off — duplicated, hard to read, structured oddly, in the wrong place. Do you:

  • Refactor it now — restructure while you’re here
  • Refactor opportunistically — wait until you touch this code for a real reason, then refactor
  • Leave it alone — accept the friction; spend the time elsewhere

The cost of refactoring is real: time, the risk of introducing bugs, the merge-conflict surface if others are working in the area.


The short answer

Default: leave it alone unless you have a concrete reason to touch it.

Refactor now when:

  • You’re about to add a feature in the area, and the current shape makes the feature harder
  • The code has caused multiple bugs (signal that the shape is wrong)
  • You can articulate what’s wrong AND what better looks like in one sentence

Refactor opportunistically (the Boy Scout rule):

  • Every time you touch a file for any reason, leave it slightly better than you found it
  • Small improvements compound; large rewrites are risky

Never refactor for:

  • “Cleaner code” as an abstract value
  • Conformance to a trend (functional > object-oriented; classes are bad; etc.)
  • Making the code “more elegant” by personal preference

The factors that matter

  1. Why do you want to refactor? Concrete reason (about to add feature X, code keeps causing bugs) → green light. “It bothers me” → red light.
  2. How well do you understand the current code? Refactoring requires deep understanding; otherwise you’ll re-implement subtle behavior incorrectly.
  3. Do tests exist? Tests give you confidence the refactor preserves behavior. Without tests, refactoring is high-risk.
  4. Is anyone else working in this code? Big refactors create merge conflicts. Coordinate.
  5. How much code changes? Small refactors (rename, extract function) are low-risk. Architectural refactors (split files, change ownership) are big projects.

When to refactor NOW

You’re about to add a feature, and the current shape blocks it

The classic case: you need to add behavior X, but X cuts across code that’s currently entangled. Refactoring first makes the feature simpler.

Bible Quest example: when adding Reading Plans (May 2026), the /admin/passages page needed a Today / Future / Past split because the existing flat list wouldn’t fit the new “plans generate future passages” model. The split happened in the same session as the feature, not as a separate refactor PR.

The code has caused multiple bugs

Each bug is a signal that the shape is wrong. Once is bad luck; twice is a pattern; three times is a refactor opportunity.

The refactor should specifically address the bug class. If you can’t say “this refactor makes that bug class impossible,” you’re refactoring for the wrong reason.

You can articulate the improvement in one sentence

“Extract the date logic into lib/datetime.ts so it’s reused, not duplicated across five places” — clear.

“Make it more modular” — not clear. Almost always a red flag.

The code reads like spaghetti to a competent reader

If reading the code takes 10 minutes to understand 50 lines, future-you (and Claude) will spend that 10 minutes every time. Refactor for legibility.


When to refactor OPPORTUNISTICALLY (the Boy Scout rule)

Every time you touch a file for a real reason, leave it slightly better:

  • Rename a confusing variable
  • Extract a 5-line helper
  • Add a one-line comment where the why is non-obvious
  • Delete a now-unused import or function

These are tiny improvements that compound. Over months, the codebase gets better without any “refactor sprint.”

Cost discipline: these improvements must be small and adjacent to your actual change. Don’t open a 200-line cleanup PR while also “fixing the bug.” Two PRs, not one.


When to LEAVE IT ALONE

”It bothers me”

Personal preference doesn’t justify the cost. If the code works and you’re not in it, walk away.

You don’t fully understand it

The bug count after a refactor is often worse than before, because subtle behavior gets dropped. If you don’t have a complete grasp of what the code does in every branch, don’t refactor it.

There are no tests

A refactor without tests is a behavior bet. You’re claiming the new code does the same thing, with no automated way to verify.

In that case: write tests first (capturing current behavior). Refactor. Re-run tests. If they pass, ship.

If the code is too entangled to test, the refactor is high-risk and probably not worth it.

It’s old code that nobody touches

If a file hasn’t been modified in 18 months, the cost of being “ugly” is bounded — nobody reads it. Refactoring it has no payoff.

You’re under deadline

Refactoring under deadline pressure is how bugs get shipped. Note the smell, file a ticket, come back later.


The kinds of refactor (and their risk levels)

KindRiskWhen to do it
Rename a variable / functionVery lowAny time
Extract a functionLowWhen the extracted unit has 2+ callers or a clear purpose
Inline a functionLowWhen the function is only called once and the abstraction wasn’t paying off
Move a function to another fileLow–mediumWhen it belongs better elsewhere (organizational)
Change a function signatureMediumWhen every caller needs to update; mass-rename via IDE
Split a module / fileMediumWhen the file has grown too large to navigate
Restructure a feature’s data modelHighOnly when you can articulate the win clearly
Rewrite a subsystem from scratchVery highAlmost never. Build alongside, migrate gradually

The risk roughly correlates with the surface area of change. Lower-risk refactors should be habitual; higher-risk ones should be deliberate projects.


What if I’ve already started?

“I’m halfway through a refactor and it’s spiraling”: stop. Revert to the last known good commit. Reassess: do you actually need this? Often the answer is “no, but I’m here now.” Revert anyway.

“I refactored and now things broke”: if you have tests, run them. If you don’t, you’re debugging in the dark. Recover what you can; document what broke; vow to write tests next time.

“I refactored and the team is upset”: big refactors need buy-in. Talk to the people who own the area BEFORE the refactor PR exists, not after.

“I want to refactor but I can’t justify it”: that’s the system working. Don’t refactor.


See also


Sources