1. Why developers confuse Strategy, Factory, and Builder
The confusion happens because all three patterns add indirection. At a quick glance, they can all appear to “hide something.” Strategy hides behavior behind an interface. Factory hides object creation behind a method or class. Builder hides object assembly behind a fluent or step-based API. If someone only looks at the surface, these patterns seem interchangeable. They are not. The easiest way to separate them is to ask one ruthless question: what is the unstable part of the code? If the unstable part is the algorithm or decision-making logic, that points toward Strategy. If the unstable part is the concrete implementation you instantiate, that points toward Factory. If the unstable part is the combination of fields, options, and construction steps needed to create a valid object, that points toward Builder.
Another reason these patterns get mixed up is that they often appear together in mature systems. A factory may create a strategy. A builder may internally use a factory to create nested objects. A payment module may use a factory to choose the provider adapter and then use a strategy to execute the pricing or retry rule. Once developers see that, they begin to assume the patterns overlap in purpose. They do not. They collaborate, but each one solves a different kind of pain. Refactoring.Guru describes patterns as a toolkit of reusable solutions rather than universal templates, which is a useful reminder that the goal is not to collect patterns like trophies, but to match the problem shape with the right tool. (
refactoring.guru)
1.1 The simplest mental model that actually works
When I review code, I usually reduce the decision to three checks. First, am I switching between multiple behaviors that follow the same contract? That is Strategy. Second, am I creating objects and trying to avoid coupling the caller to concrete classes? That is Factory. Third, am I building an object that has many optional fields, validation rules, or construction steps? That is Builder. This mental model is more useful than memorizing definitions because it maps directly to maintenance pain. Patterns are not there to impress interviewers. They are there to stop future-you from saying, “Why did I think seven overloaded constructors were a good idea?”
1.2 The cost of using the wrong pattern
Using the wrong pattern often does not fail immediately. That is why it is dangerous. If you use Strategy where a simple function would do, the code becomes ceremonious. If you use Factory when the application only ever creates one concrete class, the abstraction becomes theatrical. If you avoid Builder for a large immutable object, the constructor turns into a puzzle nobody wants to solve. The code still runs, but the maintenance cost rises. The wrong pattern is rarely a syntax problem. It is a future readability problem. That is the kind that sneaks into pull requests wearing a polite smile.
1.3 Suggested visual references for this topic
For conceptual diagrams and structure illustrations, the Strategy, Factory Method, and Builder pattern pages on Refactoring.Guru are genuinely useful because they include intent summaries and visual structure references. (
refactoring.guru)
2. When to use the Strategy pattern
Strategy is the right choice when the same task can be performed in multiple ways and you want those ways to be replaceable without modifying the code that uses them. Refactoring.Guru describes Strategy as a behavioral pattern, and its core role is to let you define a family of algorithms, put each one in a separate class, and make them interchangeable. (
refactoring.guru)
In real projects, this becomes useful when you notice condition-heavy code where behavior changes based on type, mode, or user choice. Think about payment fee calculation, discount rules, shipping cost policies, search ranking formulas, tax computation by region, retry policies for API calls, or sorting strategies for different business views. If the surrounding workflow stays mostly the same but the inner logic changes, Strategy is usually a strong fit. The key sign is that the behavior varies, not the data model. You are not creating different objects for the sake of identity. You are selecting a different way to do the same job.
2.1 A Java example of Strategy
interface DiscountStrategy {
double apply(double price);
}
class RegularDiscount implements DiscountStrategy {
@Override
public double apply(double price) {
return price;
}
}
class VipDiscount implements DiscountStrategy {
@Override
public double apply(double price) {
return price * 0.8;
}
}
class CheckoutService {
private final DiscountStrategy discountStrategy;
public CheckoutService(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public double finalPrice(double price) {
return discountStrategy.apply(price);
}
}
This example is short, but it captures the heart of the pattern.
CheckoutService does not know whether it is dealing with a regular customer, a VIP customer, or some future campaign rule. It only knows that a strategy exists and that the strategy can calculate the final price. That separation is the whole point. The checkout process depends on a contract, not on a chain of conditions. If tomorrow the business team adds a holiday discount or a member-tier discount, I do not need to modify
CheckoutService. I add another implementation of
DiscountStrategy and wire it in. That is a textbook example of moving volatility out of the stable part of the system, which aligns with the idea that patterns help code evolve with less modification to already-working logic. (
refactoring.guru)
2.2 Why this example matters in real systems
The deeper value here is not the interface itself. The deeper value is that the service becomes closed for modification but open for extension. That phrase is easy to say and hard to achieve consistently, but Strategy helps because it isolates behavior variation into small units. Each strategy can be tested independently. The checkout service becomes simpler because it delegates one concern. This also improves readability. A pricing bug is no longer buried in a 200-line method with nested conditions. It lives in the exact class where that pricing rule belongs.
There is also a team-level advantage. Different developers can work on new behaviors without colliding in the same monster method. If your codebase has many
if (type.equals(...)) branches that alter behavior, that is often a smell that Strategy is waiting nearby, sipping coffee, wondering why nobody invited it earlier. Refactoring guidance from Refactoring.Guru explicitly connects type-code-driven behavior with replacing that behavior using Strategy or State objects, which is exactly why Strategy appears so often during cleanup of branching-heavy code. (
refactoring.guru)
2.3 When Strategy is overkill
Not every variation deserves a strategy. If you only have two tiny branches and they are unlikely to grow, plain conditional logic may be clearer. Strategy starts paying off when the behaviors are meaningful, reusable, testable, or likely to expand over time. A good rule is this: if the behavior has a name, separate reasoning, and a realistic chance of change, Strategy is probably justified. If it is just one tiny conditional that will never evolve, adding five files to avoid an if statement is not architecture. It is cosplay.
3. When to use the Factory pattern
Factory is the right choice when object creation itself is becoming a source of coupling or complexity. Factory Method is defined as a creational pattern that provides an interface for creating objects while allowing the actual concrete product to vary. (
refactoring.guru)
The central problem Factory solves is not “I do not like the new keyword.” The real problem is “the caller should not need to know which concrete implementation to instantiate.” That distinction matters. When a service starts depending on PaypalPayment, StripePayment, CryptoPayment, and BankTransferPayment directly, the service is no longer just using payment logic. It is making creation decisions, which couples it to concrete classes and makes expansion harder. Factory pulls that decision into one controlled place.
3.1 A Java example of Factory
interface NotificationSender {
void send(String message);
}
class EmailSender implements NotificationSender {
@Override
public void send(String message) {
System.out.println("Send email: " + message);
}
}
class SmsSender implements NotificationSender {
@Override
public void send(String message) {
System.out.println("Send SMS: " + message);
}
}
class NotificationFactory {
public static NotificationSender create(String channel) {
if ("email".equalsIgnoreCase(channel)) {
return new EmailSender();
}
if ("sms".equalsIgnoreCase(channel)) {
return new SmsSender();
}
throw new IllegalArgumentException("Unsupported channel: " + channel);
}
}
This code is intentionally small, but the architectural effect is large. The rest of the application can now call NotificationFactory.create(channel) and receive a NotificationSender without knowing the concrete type. The creation rule is centralized. If object setup becomes more complicated later, maybe requiring configuration, credentials, fallback dependencies, or region-based selection, that complexity stays inside the factory layer rather than leaking across multiple call sites.
3.2 Why Factory is valuable beyond hiding constructors
The real gain from Factory is control. You decide one place where object selection happens. That reduces duplication and prevents scattered construction logic. Suppose email senders need SMTP configuration while SMS senders need API credentials. Without a factory, these setup details often spread across the codebase. With a factory, the caller only asks for a sender; the factory deals with the messy details. This matches the creational purpose of Factory Method: it separates the act of using an object from the logic of deciding which concrete object should exist. (
refactoring.guru)
Factory also becomes more useful when the set of possible implementations changes over time. Plugins, adapters, storage providers, payment gateways, cloud vendor clients, report exporters, and parser implementations are classic examples. If new types appear regularly, direct construction in business code becomes a maintenance trap. A factory gives you a seam where that growth can happen more safely.
3.3 When Factory is the wrong answer
Factory is not automatically useful just because there is object creation. If you create a simple DTO or a plain service that has exactly one concrete implementation, adding a factory may add indirection without reducing pain. A good smell for Factory is not “there is a constructor.” A good smell is “the caller should not be deciding the concrete class.” If that is not true, a factory may be unnecessary. Sometimes new UserDto(...) is just new UserDto(...). No mystery. No ritual. No ceremony needed.
4. When to use the Builder pattern
Builder is the right choice when object construction is complex, verbose, easy to misuse, or packed with optional parameters. Refactoring.Guru describes Builder as a creational pattern that lets you construct complex objects step by step and produce different representations using the same construction process. (
refactoring.guru)
The classic case is a constructor with too many arguments. Even worse is when many of those arguments are optional or of similar types. That is how bugs are born. new User(true, false, "admin", "UTC", "en", null, null, 3) is the kind of code that technically compiles and spiritually collapses. Builder helps by turning a fragile positional constructor into a readable, controlled construction flow.
4.1 A Java example of Builder
class UserProfile {
private final String username;
private final String email;
private final String phone;
private final boolean active;
private UserProfile(Builder builder) {
this.username = builder.username;
this.email = builder.email;
this.phone = builder.phone;
this.active = builder.active;
}
public static class Builder {
private final String username;
private String email;
private String phone;
private boolean active = true;
public Builder(String username) {
this.username = username;
}
public Builder email(String email) {
this.email = email;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Builder active(boolean active) {
this.active = active;
return this;
}
public UserProfile build() {
return new UserProfile(this);
}
}
}
And the usage looks like this:
UserProfile profile = new UserProfile.Builder("anhtran")
.email("anh@example.com")
.phone("123456789")
.active(true)
.build();
This is clearer than a long constructor because each value is attached to its meaning. That sounds simple, but it dramatically lowers the chance of passing the wrong value into the wrong position. It also makes the code easier to read during reviews. When someone scans this construction flow, they immediately understand which fields are required and which are optional. The builder owns the assembly story.
4.2 Why Builder improves design, not just syntax
The biggest win of Builder is not visual neatness. It is correctness. A builder can enforce required fields, apply validation in build(), compute derived defaults, and keep the final object immutable. Those are meaningful design benefits. When the construction of a valid object has rules, Builder becomes more than a convenience; it becomes a safety boundary.
This is especially useful for domain models, API request payloads, configuration objects, search criteria, report definitions, and test data creation. In those cases, object creation is not trivial. It carries meaning. Builder preserves that meaning. Refactoring.Guru emphasizes that Builder is suited to step-by-step construction where not all steps are always required, which is exactly why it handles optional configuration so well. (
refactoring.guru)
4.3 When Builder is unnecessary
Builder becomes excessive when the object is tiny and stable. If a class has two or three required fields and no optional complexity, a normal constructor is usually better. A builder for a class like Point(x, y) is not elegant. It is a cry for help. Builder should solve a real construction problem, not exist because fluent APIs look fashionable in conference slides.
5. How to choose between Strategy, Factory, and Builder in practice
The easiest way to choose is to focus on the pressure point in the code. If you are wrestling with a growing set of interchangeable algorithms, use Strategy. If you are wrestling with selection and instantiation of concrete implementations, use Factory. If you are wrestling with object creation that has too many parameters or rules, use Builder.
A useful thing to notice is that these patterns answer different verbs. Strategy answers “execute.” Factory answers “create.” Builder answers “assemble.” Once I started framing them through verbs, the confusion dropped sharply. The wrong pattern usually reveals itself when the main verb does not match the problem. If the code pain is about behavior and I reach for Builder, I am probably solving the wrong problem. If the pain is about construction readability and I reach for Strategy, I am definitely solving the wrong problem.
5.1 A practical comparison through one business scenario
Imagine an e-commerce system. The discount rule for customers varies by customer type and campaign. That is Strategy because the algorithm for calculating price changes. The notification sender differs by channel such as email or SMS. That is Factory because the application needs to choose which concrete sender object to create. The order request contains many optional fields such as coupon, shipping notes, gift wrapping, invoice details, and delivery preferences. That is Builder because creating a valid request object cleanly matters more than raw constructor speed.
In one system, all three patterns may coexist, and that is fine. It does not mean the design is overengineered. It means different kinds of variability are being handled with appropriate tools. The trick is not to force one pattern to solve every problem just because it worked nicely in the previous module.
5.2 Questions I ask before choosing a pattern
When I need to decide quickly, I ask myself whether I am trying to swap behavior, hide concrete creation, or simplify complex construction. Those three questions usually expose the right pattern fast. If none of them fit, maybe I do not need any pattern yet. That is also a valid outcome. The cleanest design is often the one that earns its abstractions instead of preordering them like expensive furniture for a room that does not exist yet.
6. Common mistakes when using these patterns
A common mistake with Strategy is creating too many microscopic strategy classes for logic that is trivial and unlikely to change. A common mistake with Factory is turning every constructor call into a factory even when no variation exists. A common mistake with Builder is using it for every model class, including simple objects that would be clearer with a constructor or static factory method. These mistakes all come from the same habit: applying patterns because they are famous, not because the code is asking for them.
Another mistake is mixing responsibilities. For example, a factory that also starts executing business rules becomes muddy. A builder that begins making runtime policy decisions is no longer just assembling objects. A strategy that is responsible for choosing which strategy to use has wandered into factory territory. Patterns work well when their responsibility lines stay clean. The moment those lines blur, the abstractions stop helping and start negotiating with each other like bored managers in a meeting that should have been an email.
6.1 The “future-proofing” trap
Many developers justify pattern-heavy code by saying they are preparing for future changes. Sometimes that is wise. Often it is speculative architecture. Patterns should be introduced when change is probable, costly, or already visible in the current code. If the only reason for adding a pattern is “maybe someday,” the abstraction may age badly before the use case ever arrives. Good design is not about maximum flexibility at all costs. It is about the right flexibility at the right point.
6.2 The maintainability rule that matters most
The pattern is correct only if the next developer understands the code faster because of it. That is the rule I trust most. If the abstraction hides complexity and reveals intention, it is helping. If it hides simple things behind unnecessary ceremony, it is hurting. Patterns should compress mental load, not expand it.
7. Final takeaway: the right pattern depends on what is changing
If I strip away the terminology, the answer is beautifully practical. I use Strategy when I need multiple interchangeable ways to perform the same job. I use Factory when I want to separate callers from concrete class creation. I use Builder when creating an object becomes too complex, too error-prone, or too unreadable with plain constructors. Strategy protects behavioral variation. Factory protects creation variation. Builder protects construction clarity.
That distinction matters because software rarely becomes painful all at once. It becomes painful through small repeated decisions. Another conditional here. Another direct instantiation there. Another constructor parameter nobody can remember by position. Patterns are valuable when they stop these small decisions from turning into system-wide friction. Used well, they make the codebase easier to extend, easier to test, and easier to reason about. Used badly, they turn straightforward code into a museum of interfaces. The goal is not to be pattern-rich. The goal is to be maintenance-smart.
If you want a concise rule to remember, here it is. When behavior changes, think Strategy. When concrete types change, think Factory. When object construction gets ugly, think Builder. That rule is not academically fancy, but it survives real code reviews far better than most polished whiteboard definitions.
Want to ask anything, comment below.
8. References for visual learning
For image-backed explanations and pattern structure diagrams, these pages are useful starting points: Strategy pattern, Factory Method pattern, Builder pattern, and the broader design pattern catalog. (
refactoring.guru)
Read more at : Methods to Know Exactly When to Use Strategy, Factory, and Builder in Java