skip to content

What does "refactoring to patterns" mean, and why is introducing a design pattern through refactoring often preferred over choosing patterns up front?

level: juniorimportance: must knowfreq 58%

answer

  1. smell → refactoring steps → pattern
  2. pattern is destination, not starting point
  3. Kerievsky: toward / further toward / away
  4. avoid speculative generality
  5. needs tests to preserve behavior

basics

~20 s

It means you don't pick patterns before writing code. You write the simplest thing that works, and when the code starts to smell (duplication, a growing conditional), you refactor in small behavior-preserving steps until a known pattern emerges. The smell justifies the pattern.

solid answer

~50 s

Named after Joshua Kerievsky's book, refactoring to patterns is the practice of letting a design pattern be the destination of a refactoring rather than an up-front design decision. You start with the simplest design that passes the tests. When real duplication, a growing conditional, or a rigid dependency appears, you name the smell and apply a sequence of small behavior-preserving refactorings whose end state happens to be a known pattern (Strategy, State, Decorator, Template Method, Factory). The benefit is evidence: the pattern is added only where the code has already proved it needs the flexibility, so you avoid speculative generality and the indirection, extra types, and cognitive load a pattern costs. It also lowers risk, since each step is small and test-covered rather than a big-bang redesign. Kerievsky frames three directions: refactoring toward a pattern, further toward it, and away from one that no longer earns its keep.

go deeper

for a junior

Define refactoring (structure changes, behavior preserved, tests green) and say patterns should emerge from smells like duplication or a growing conditional, not be chosen up front.

for a middle

Name concrete smell→pattern pairs and the small steps used to get there (Extract Function/Class, Extract Interface), and mention that you can stop partway.

for a senior

Frame the trade-off explicitly: cost of speculative generality vs. cost of delaying, which decisions are reversible, and the need for characterization tests in legacy code.

for a principal

Talk about it as an organizational stance — evolutionary design with a strong test/CI backbone, which decisions are genuinely one-way doors, and how to keep teams from both pattern-happiness and abstraction paralysis.

## Terms first **Refactoring** = changing the internal structure of code *without changing its externally observable behavior*, in small steps, with tests green before and after each step. Extract Function, Rename, Pull Up Method, Extract Interface are typical steps. **Design pattern** = a named, recurring solution to a recurring design problem, with a known structure, known consequences, and a name the team can say out loud ("this is a Strategy"). **Code smell** = a surface symptom in code that usually indicates a deeper design problem: duplicated code, a long method, a switch/if-chain on a type code, a class that changes for many reasons, a flag parameter that toggles behavior. **Refactoring to patterns** = combining the three: a smell is the *evidence*, a sequence of refactorings is the *mechanism*, a pattern is the *destination*. ## The problem it solves The classic failure mode after learning the GoF catalog is *pattern-happy design*: choosing Abstract Factory, Visitor, and a plugin registry on day one "because we'll need it". That is **speculative generality** — flexibility paid for now against a requirement that may never arrive. The costs are real and immediate: - more types and files to navigate; behavior scattered across indirection layers; - harder debugging (the call you want is behind two virtual dispatches); - the abstraction is usually guessed at the wrong seam, because you had one example, and one example never reveals the axis of variation; - it is *harder to remove* a wrong abstraction than to add a right one later. The opposite failure exists too: never introducing patterns, so a 400-line conditional keeps growing. Refactoring to patterns is the middle path — **evolutionary design**: simplest thing now, restructure when the code tells you. ## How the practice actually runs 1. **Notice the smell.** "This switch on `type` has appeared in three methods." "Every subclass repeats the same five steps in the same order." 2. **Name the target pattern**, but hold it loosely — it is a hypothesis about where the code wants to go. 3. **Take small steps**, each independently green: Extract Function, Extract Class, Move Function, Extract Interface, Replace Constructor with Factory Method, Pull Up / Push Down. 4. **Stop when the pain stops.** Kerievsky's point is you may refactor only *partway* toward a pattern and that is fine — a couple of extracted classes may remove the smell without the full catalog structure. ## Kerievsky's three directions - **Toward a pattern** — the smell says the pattern's forces are present (e.g. Replace Conditional with Strategy). - **Further toward a pattern** — a partial structure exists and more variation arrives, so you complete it. - **Away from a pattern** — the pattern was over-applied or the requirement disappeared, so you inline it back (e.g. Inline Singleton, collapse a one-implementation Strategy). That third direction is what separates this practice from "pattern advocacy": patterns are not a one-way ratchet. ## Preconditions and edge cases - **You need tests.** Behavior preservation is only a claim without them; with legacy code, write characterization tests first. - **Not everything should be deferred.** Cross-cutting, hard-to-reverse decisions — persistence boundaries, public API shape, security boundaries, module topology — deserve deliberate up-front design, because refactoring them later is not a local change. Refactoring to patterns is strongest for *internal* class-level design. - **Refactoring ≠ rewriting.** If you delete and re-implement, you lost the safety property that makes the practice cheap. ## The short version to say in an interview "Patterns are a destination, not a starting point. I let a smell justify the pattern, get there in small green steps, and I'm equally willing to refactor back out when the pattern stops paying for itself."

  • Are there design decisions you would NOT defer to a later refactoring?
    Yes — decisions that are expensive or impossible to reverse locally: module and service boundaries, the persistence and transaction model, public API/wire contracts, security and authorization boundaries, and data migration strategy. Those deserve deliberate up-front design; refactoring to patterns targets internal, class-level structure where a change stays inside one codebase and can be made in green steps.
  • What do you need in place before you start refactoring toward a pattern in legacy code?
    A test harness around the behavior you're about to move. If unit tests don't exist, write characterization tests (tests that pin current behavior, whatever it is), often via a seam or an approval/golden-master test. Without them you can't claim behavior preservation, and the refactoring becomes a rewrite with unknown risk.

You don't pave a walkway across a lawn on day one and hope people use it. You wait for the desire path worn by actual foot traffic, then pave that. The pattern is the pavement; the smell is the worn grass.

saying these in an interview costs you the question

  • "Good architects choose the right patterns before coding" — treats patterns as up-front deliverables and produces speculative generality.
  • "More patterns means better design" — patterns have costs (indirection, types, cognitive load) and must be earned.
  • "Refactoring to a pattern means rewriting the class" — it's a sequence of small behavior-preserving steps, each test-green.
  • "Once you introduce a pattern you shouldn't remove it" — refactoring away from patterns is an explicit, equally valid direction.
  • Confusing refactoring with adding features; a refactoring changes structure only, never observable behavior.

context