skip to content

What are the costs and failure modes of Factory Method, and when would you deliberately not use it?

level: seniorimportance: should knowfreq 40%

answer

  1. parallel hierarchies double the class count
  2. can't see what you get by reading the line
  3. inheritance = one axis, sealed types blocked
  4. param leakage → abstraction erodes
  5. one implementation → don't; extract the seam later

basics

~20 s

It adds a class hierarchy and indirection: more types to read, harder to see what actually gets created, and extension only via subclassing. Skip it when there's one implementation, when a plain function or injected dependency suffices, or when selection is data-driven.

solid answer

~60 s

Costs: (1) **parallel hierarchies** — a creator subtype per product branch roughly doubles type count; (2) **traceability** — "what object do I get here?" needs runtime knowledge of the wiring, hurting debugging and static reading; (3) **inheritance as the extension mechanism** — you can't use it if the creator is sealed, if a variant needs two axes of variation (multiple inheritance problem), or if selection depends on runtime data rather than the creator's type; (4) **parameter leakage** — when one product needs construction data others don't, the factory-method signature grows a union of everything, eroding the abstraction; (5) **speculative generality** when only one implementation ever exists. Don't use it when: there is one concrete class and no strong evidence of a second; a constructor injected dependency or a `Supplier`/factory *function* gives the same decoupling without a hierarchy; a DI container already resolves the implementation; or the choice comes from data, where a registry is a better fit. Prefer composition (an injected factory object) over inheritance when the creator's own logic doesn't need to vary alongside creation.

go deeper

for a junior

Mention the obvious cost: more classes and more indirection, so don't use it when there's only one implementation.

for a middle

Add traceability and inheritance rigidity, and name the lighter alternatives: injected dependency, supplier function, DI container.

for a senior

Discuss parameter leakage, multi-axis variation forcing composition over inheritance, constructor/virtual-call hazards, and the refactoring argument for deferring the abstraction.

for a principal

Frame it as variation-point economics: predicting axes of change is unreliable, seams are cheap to add later, and organization-wide factory boilerplate imposes real cognitive cost; set guidance on when factories are warranted.

## Real costs ### 1. Class-count and parallel hierarchies GoF's own "Consequences" section flags this: subclassing purely to change the created product means every product branch drags a creator branch along. Two products, three contexts → potentially six classes carrying no behaviour except a `return X()`. Reading the code now requires holding a type map in your head. ### 2. Loss of local reasoning A constructor call tells you exactly what you get. A factory method tells you only the declared return type. Debugging shifts from "read the line" to "find which creator was wired", which can mean tracing a container configuration or a plugin manifest. Mitigations: log the chosen implementation at startup, keep the wiring in one composition root, and name creators after their environment. ### 3. Inheritance is a rigid extension mechanism - Only one axis of variation per hierarchy — varying by transport *and* by billing region wants two axes, which single inheritance cannot express (composition can). - Sealed/final creators, or creators you don't own, cannot be extended. - Frameworks that require a no-arg construction path or that proxy classes may interact badly with subclassing. - **Virtual call from a constructor**: if the base constructor calls the factory method, the subtype's override may run before the subtype's fields are initialized, yielding partially constructed state or null fields. Create lazily in a regular method instead. ### 4. Parameter leakage / interface erosion If `createTransport()` must become `createTransport(port, refrigeration, hazmatClass)` because *one* product needs those, the abstraction has absorbed the details it was hiding. Symptoms: parameters ignored by most implementations, nullable arguments, or `Map<String, Any>` config bags. Usually a sign that products aren't actually substitutable, or that a builder/parameter object belongs in the picture. ### 5. Speculative generality Applying the pattern "in case we need another implementation" is a bet. If the second implementation never appears — or appears with different requirements than you guessed — you've paid indirection forever for nothing. Adding the seam later is usually a mechanical refactor ("extract factory method"), so deferring is cheap; over-abstracting early is not. ### 6. Testing subtleties The pattern *helps* tests (substitute a creator returning fakes). But the "override the factory method in a test subclass" trick — a partial mock / *subclass-and-override* seam — couples tests to the production class's internal structure and can hide real construction bugs. Prefer injecting a factory collaborator. ## When not to use it | Situation | Better choice | |---|---| | Exactly one implementation, no realistic second | Call the constructor; extract a seam later if needed | | Caller just needs *an* instance, no algorithm shared | Inject the dependency, or a `Supplier`/provider function | | A DI container already selects the implementation | Container configuration is the factory | | Selection from runtime data (DB discriminator, message type) | Registry: map key → creator/supplier | | Variants differ by *state*, not class | Prototype (clone a configured exemplar) or a parameterized constructor | | Two independent axes of variation | Composition — inject a factory object (Strategy for creation) | | Complex parameter assembly, not implementation choice | Builder or a parameter object | ## Language-neutral note on lightweight alternatives In languages with first-class functions, a creator can simply take a function `() -> Product`. This gives the same decoupling and testability with zero new classes, is trivially composable, and supports multiple axes (pass several functions). Many modern codebases treat Factory Method's inheritance form as the historical formulation and reach for a supplier/provider parameter instead. Interviewers value candidates who know both and can say why the lighter option is usually sufficient — while still recognizing the inheritance form when the creator's own logic genuinely varies alongside creation (which is when Template Method + Factory Method together earn their keep). ## Smells that you've misapplied it - Creator subclasses whose only member is the factory method override, and there's exactly one of them. - `instanceof` / type-switch on the returned product somewhere downstream. - A factory method with a `kind` parameter *and* a subtype hierarchy (two selection mechanisms fighting). - Factory interfaces mirroring every domain class one-to-one, mechanically.

  • You inherit a codebase where every domain class has a matching factory interface and implementation, all with a single implementation each. What do you do?
    Treat it as speculative generality. Verify each factory has exactly one implementation and no test double relying on it, then inline the trivial ones toward direct construction or container wiring, keeping factories only where creation carries real logic (validation, caching, environment-based selection) or a genuine second implementation exists.
  • Why is calling an overridable factory method from a base-class constructor risky?
    In languages with virtual dispatch during construction, the subtype's override executes before the subtype's own fields are initialized, so it can read uninitialized/null state or leak a partially constructed object. Create the product lazily in a normal method or pass it in instead.

saying these in an interview costs you the question

  • "More patterns means better design" — each variation point has a permanent readability and maintenance cost.
  • Adding a creator hierarchy for a single implementation, "just in case".
  • Using subclass-and-override of the factory method as the standard testing seam instead of injecting a factory.
  • Growing the factory-method signature with parameters only one product uses.
  • Claiming Factory Method can pick a class from runtime data without a registry or a parameterized switch.
  • Calling the overridable factory method from the base constructor.

context