
Factory Pattern: The Object Factory
Client says 'Pizza please', Factory handles the baking logic. Hiding creation complexity.

Client says 'Pizza please', Factory handles the baking logic. Hiding creation complexity.
Why does my server crash? OS's desperate struggle to manage limited memory. War against Fragmentation.

Two ways to escape a maze. Spread out wide (BFS) or dig deep (DFS)? Who finds the shortest path?

Fast by name. Partitioning around a Pivot. Why is it the standard library choice despite O(N²) worst case?

Establishing TCP connection is expensive. Reuse it for multiple requests.

First learning design patterns, senior left code review:
// My code
const button = new IOSButton();
Review: "Use Factory pattern."
Me: "Factory? Can't I just new?"
Senior: "Does customer enter kitchen to bake bread?"
"????"I genuinely didn't understand. Why complicate creating one object? Why add another class called Factory? Can't I just use the new keyword? This confusion sparked my journey into Factory patterns.
I was building a cross-platform app supporting iOS, Android, and Web. Back then, I didn't know much about design patterns. I just created objects whenever needed.
My code looked like this:
// ❌ Button creation (platform-specific)
if (platform === "ios") {
button = new IOSButton();
} else if (platform === "android") {
button = new AndroidButton();
} else if (platform === "web") {
button = new WebButton();
}
At first, no problem. A few places creating buttons? Fine. But as the app grew, this code duplicated across 100+ locations. Every UI component (Input, Checkbox, Modal, Dropdown) needed platform-specific logic.
Then one day, product manager said: "Let's add Windows desktop support."
Panic mode. Adding Windows meant finding and editing 100+ places. Miss one? Bugs. Endless testing. Nightmare fuel.
Senior: "Factory pattern = edit 1 place only."
That's when I seriously started studying Factory patterns.
The more I read about Factory patterns, the more confused I got.
First confusion: "Can't I just wrap it in a function?"
I thought this would work:
function createButton(platform) {
if (platform === "ios") return new IOSButton();
if (platform === "android") return new AndroidButton();
return new WebButton();
}
Isn't this a factory? Why create classes, define interfaces, use inheritance, make it so complicated?
Second confusion: "Simple Factory, Factory Method, Abstract Factory... what's different?"
Design pattern books list THREE factories. GoF doesn't even recognize Simple Factory as official. They all looked similar to me. What's the actual difference?
Third confusion: "Why is this a pattern? Isn't it just a function?"
Most puzzling: Factory Pattern is basically "a function that creates objects," right? Then it's just a function. Why the grandiose name "pattern"? I pondered this for days.
Over lunch, I asked my senior: "How is Factory pattern different from a regular function?"
He held up his Starbucks cup:
"Oh, hide creation logic!""Ordering Americano at Starbucks:
Customer (Client): 'One Americano please.'
Barista (Factory): (Grinds beans, extracts espresso, adds water...) 'Here you go!'
Customer doesn't know creation process. Even if recipe changes, customer orders same way.
Factory Pattern = Barista"
That moment, I got it. Factory pattern's core isn't "object creation"—it's "encapsulating creation logic."
Without barista, customer makes coffee themselves? Customer must know where beans are, how to use machine, water temperature settings. Recipe changes? Every customer relearns.
Same with Factory pattern. Client code doesn't deal with complex constructors like new IOSButton(config, theme, language, permissions, ...). Factory says "Hey, just tell me the platform. I'll handle the rest."
After this analogy, everything clicked. Why factories exist, why they're patterns—it made perfect sense.
Let me walk through what happens without factories. These are problems I actually faced.
Building cross-platform app, I needed storage, camera, push notifications implemented per-platform. My code:
// Repeated 100 places
if (OS === "ios") {
const storage = new IOSStorage(config);
const camera = new IOSCamera(settings);
const push = new IOSPushNotification(apiKey);
} else if (OS === "android") {
const storage = new AndroidStorage(config);
const camera = new AndroidCamera(settings);
const push = new AndroidPushNotification(apiKey);
}
Fine initially. When features numbered around 10. But as app grew, this pattern repeated 100+ times.
First problem: Code duplication. Same if-else logic 100 places. Fixing bugs or changing logic? Find and edit 100 locations. Miss one? Bugs.
Second problem: Tight coupling. Client code directly depends on concrete class names like IOSStorage, AndroidStorage. Later rename classes or change constructor arguments? Edit 100 places.
Third problem: Hard to extend. Adding Windows support? Find 100 places, add else if (OS === "windows") branches. The nightmare I mentioned.
Fourth problem: Exposed creation logic. Client must know all detailed arguments: config, settings, apiKey. New developers must figure out "to create this object, what's config, what's settings..."
After this experience, I deeply felt the need for Factory pattern.
First attempt: Simple Factory. Most intuitive and easiest to understand.
class PlatformFactory {
static createButton(platform) {
switch (platform) {
case "ios":
return new IOSButton();
case "android":
return new AndroidButton();
case "web":
return new WebButton();
default:
throw new Error(`Unknown platform: ${platform}`);
}
}
}
// Usage
const button = PlatformFactory.createButton("ios");
button.render();
Immediately code got cleaner. 100 scattered if-else logics consolidated to one place.
First benefit: Centralized creation logic. Button creation logic lives only in PlatformFactory. Later changes or bug fixes? Look one place.
Second benefit: Simplified client code. Client no longer worries about if (platform === "ios") branches. Just call PlatformFactory.createButton("ios").
But days later, problems emerged.
First problem: Modifying code when adding platforms. Adding Windows? Open PlatformFactory class, add case "windows": branch. Why problematic?
Violates OCP (Open-Closed Principle). "Open for extension, closed for modification." Simple Factory requires modifying existing code to extend.
Initially I thought "at least just 1 place to edit," but with growing team, this became problematic. If I'm modifying PlatformFactory while colleague modifies same file? Merge conflicts.
Factory Method pattern solves this.
Factory Method is my most-used factory pattern. Elegantly solves Simple Factory's problems.
Simple Factory requires code modification for extension. Adding new platforms means adding case to switch.
Factory Method lets subclasses decide object creation:
// Creator (Abstract)
class UIFactory {
createButton() {
throw new Error("Must implement createButton()");
}
render() {
const button = this.createButton();
button.render();
}
}
// Concrete Creators
class IOSFactory extends UIFactory {
createButton() {
return new IOSButton();
}
}
class AndroidFactory extends UIFactory {
createButton() {
return new AndroidButton();
}
}
// Usage
const factory = OS === "ios" ? new IOSFactory() : new AndroidFactory();
factory.render(); // Automatically creates correct button
First seeing this: "Hmm... kinda complex?" More code than Simple Factory. But in practice, difference was clear.
First advantage: OCP compliance. Adding Windows? Just create WindowsFactory class. Existing UIFactory, IOSFactory, AndroidFactory code? Zero modifications needed.
// Adding Windows - no existing code touched!
class WindowsFactory extends UIFactory {
createButton() {
return new WindowsButton();
}
}
Second advantage: Extensibility. Multiple team members can create different Factories independently. I work on IOSFactory while colleague builds AndroidFactory. No code conflicts.
Third advantage: Framework design. Building UI library? Provide UIFactory only, tell users "implement button creation yourself." Library users create their CustomFactory, generate buttons their way.
Understanding this pattern, I finally grasped why Spring Framework's Bean Factory is designed that way.
Using Factory Method, another problem surfaced: creating multiple related objects simultaneously.
For iOS UI, need Button, Input, Checkbox all in iOS style. But accidentally mix IOSButton with AndroidCheckbox? UI disaster.
Abstract Factory solves this:
// Abstract Factory
class PlatformFactory {
createButton() { throw new Error(); }
createInput() { throw new Error(); }
createCheckbox() { throw new Error(); }
}
// Concrete Factory (iOS)
class IOSFactory extends PlatformFactory {
createButton() {
return new IOSButton();
}
createInput() {
return new IOSInput();
}
createCheckbox() {
return new IOSCheckbox();
}
}
// Concrete Factory (Android)
class AndroidFactory extends PlatformFactory {
createButton() {
return new AndroidButton();
}
createInput() {
return new AndroidInput();
}
createCheckbox() {
return new AndroidCheckbox();
}
}
// Usage
function createForm(factory) {
const button = factory.createButton();
const input = factory.createInput();
const checkbox = factory.createCheckbox();
return { button, input, checkbox };
}
const factory = OS === "ios" ? new IOSFactory() : new AndroidFactory();
const form = createForm(factory); // Consistent UI components
Pattern's core: Guaranteed consistency. Call createForm(new IOSFactory()), Button, Input, Checkbox all created iOS-styled. No accidental mixing.
First advantage: Consistency. Only same-platform components generated, preventing absurdities like iOS button with Android checkbox.
Second advantage: Uniformity. Changing themes or switching platforms? Just swap Factory, entire UI transforms at once.
I tried this pattern for dark/light mode switching—super convenient. Just create LightThemeFactory and DarkThemeFactory, theme switching becomes one line.
Abstract Factory shines when:
Theory alone fades quickly. I truly understand through practice. Sharing code I built at work.
Building user profile page. Needed different avatars based on user tier (free/premium/verified).
Initial code:
// Duplicated 100 places
function UserProfile() {
let avatar;
if (user.isPremium) {
avatar = <PremiumAvatar user={user} size="large" border="gold" />;
} else if (user.isVerified) {
avatar = <VerifiedAvatar user={user} size="medium" border="blue" />;
} else {
avatar = <BasicAvatar user={user} size="small" />;
}
return <div>{avatar}</div>;
}
Problem: this logic repeated across 100 places—profile page, comments, chat, feed. Adding VIP tier? Edit 100 locations.
Applied Factory pattern:
// Factory
class AvatarFactory {
static create(user) {
if (user.isPremium) {
return <PremiumAvatar user={user} size="large" border="gold" />;
} else if (user.isVerified) {
return <VerifiedAvatar user={user} size="medium" border="blue" />;
} else {
return <BasicAvatar user={user} size="small" />;
}
}
}
// Usage (1 line!)
function UserProfile() {
return <div>{AvatarFactory.create(user)}</div>;
}
Now adding VIP tier? Just modify AvatarFactory. Leave 100 client code locations untouched.
This experience taught me: Factory pattern excels at isolating frequently-changing logic. User tiers change often—adding new tiers, modifying existing ones, adjusting tier benefits. These changes affecting only one place (Factory) made maintenance vastly easier.
Studying Java Spring, encountered Bean Factory. Initially thought "What's this?" But understanding Factory pattern, Spring's design became clear.
// ApplicationContext = Factory
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// Bean creation (Factory handles dependency injection)
UserService userService = context.getBean(UserService.class);
Spring's ApplicationContext is a giant Factory. Developers just ask "UserService please," Spring automatically creates UserService, injects required dependencies (UserRepository, PasswordEncoder, etc.).
@PostConstruct) and destruction (@PreDestroy).All applications of Factory pattern!
Learning Spring initially: "Why so complex?" After understanding Factory pattern: "Oh, this all encapsulates creation logic."
Developers don't directly create objects like new UserService(new UserRepository(), new PasswordEncoder()). Just ask "UserService please," Spring Factory handles it.
This is the real-world version of "customer doesn't enter kitchen."
Sharing DB connection Factory I actually built. This code runs in my side project.
class DatabaseFactory {
static createConnection(env) {
const config = this.getConfig(env);
switch (config.type) {
case "mysql":
return new MySQLConnection(config);
case "postgres":
return new PostgresConnection(config);
case "mongodb":
return new MongoDBConnection(config);
default:
throw new Error(`Unknown DB: ${config.type}`);
}
}
static getConfig(env) {
if (env === "production") {
return {
type: "postgres",
host: "prod-db.company.com",
port: 5432
};
} else {
return {
type: "mysql",
host: "localhost",
port: 3306
};
}
}
}
// Usage
const db = DatabaseFactory.createConnection("production");
db.query("SELECT * FROM users");
My service uses MySQL in development, PostgreSQL in production. Without factory:
// ❌ Without factory
let db;
if (env === "production") {
db = new PostgresConnection({
host: "prod-db.company.com",
port: 5432,
user: "admin",
password: process.env.DB_PASSWORD
});
} else {
db = new MySQLConnection({
host: "localhost",
port: 3306,
user: "root",
password: "1234"
});
}
This code would repeat everywhere needing DB connection (controllers, services, test code). Later switch to MongoDB or change connection settings? Hell.
Using Factory, client code unaffected by environment changes. Just call DatabaseFactory.createConnection(env).
Actually using it, these benefits stood out:
Not handling this complex logic in every client code keeps things much cleaner.
Studying Factory pattern, biggest question: "When should I use it?" Using factories for every object creation is over-engineering. Not using creates code duplication.
My experience suggests using factories when:
// ❌ Don't repeat this 100 times
const db = new Database({
host: "db.example.com",
port: 5432,
user: "admin",
password: process.env.DB_PASSWORD,
ssl: true,
poolSize: 20,
timeout: 30000,
retryAttempts: 3
});
// ✅ Wrap in Factory
const db = DatabaseFactory.create("production");
2. Different objects per platform/environment
// iOS Button vs Android Button
// MySQL Connection vs PostgreSQL Connection
// Light Theme vs Dark Theme
Most common in my experience: cross-platform apps, multi-environment deployments, theme systems.
3. Object composition needs consistency// Dark theme → Button, Input, Checkbox all Dark
// iOS → Button, Input, Checkbox all iOS-styled
Abstract Factory shines here.
4. Creation approach changes frequentlyUser tiers, plans, permissions change often. Scattering this logic across 100 places? Maintenance hell.
But over-engineering is also problematic. Don't use factories when:
// ❌ Over-engineering
class UserFactory {
static createUser(name) {
return new User(name);
}
}
// ✅ Just create directly
const user = new User("Alice");
If User object is simple, creation logic won't change, no platform variations? Just use new User(). Factory only adds complexity.
My principle: "Use only when needed." Don't blindly apply Factory pattern everywhere. Use only when genuinely needing to encapsulate creation logic.
Most confusing while studying: distinguishing Simple Factory, Factory Method, Abstract Factory. My summary:
| Pattern | Characteristic | Implementation | Pros | Cons | Example |
|---|---|---|---|---|---|
| Simple Factory | One method with branching | static method with switch | Simple, easy to understand | Violates OCP | createButton(type) |
| Factory Method | Subclasses decide creation | Inherit abstract class | OCP compliant, extensible | More classes | IOSFactory.createButton() |
| Abstract Factory | Creates multiple related objects | Multiple create methods | Guarantees consistency | Increased complexity | IOSFactory.createButton/Input/Checkbox() |
My practical selection criteria:
Starting with Simple Factory, refactoring to Factory Method when needed seems most realistic.
My thinking completely transformed before and after studying Factory pattern.
Before: "Just use new, why complicate?"
After: "Exposing creation logic to client code creates future hell. Delegate to Factory."
I summarized Factory pattern's core as four points:
When I first asked "Can't I just use new?", now I understand what my senior meant:
"Object creation also needs separation of concerns.""
newkeyword is powerful but dangerous. If you scatternew IOSButton()everywhere, changingIOSButtonconstructor later becomes hell. Delegate to Factory. Changes in one place."
Now during code reviews, I say: "Customer doesn't enter kitchen. Order from Factory."
I accepted that Factory pattern isn't merely about creating objects—it's a mindset for change-resilient design. This realization completely transformed my design philosophy.