Patterns rarely appear alone. How do you reason about combining patterns in one design — which combinations are natural, which signal a modelling mistake, and how do you keep a large codebase's pattern usage coherent?
answer
- each pattern answers a distinct question
- Composite+Visitor: nodes cheap vs operations cheap
- wiring > behaviour = over-combination
- one idiom per recurring problem, codebase-wide
- prune single-implementation abstractions
basics
~20 sCombine patterns when each answers a different question — for example a factory choosing a strategy, or a composite of commands. It is a warning sign when several patterns solve the same question, when wiring code outweighs domain code, or when nobody can describe the runtime object graph.
solid answer
~50 sPatterns compose along different concerns, so a natural combination has each pattern answering a distinct question: what varies (Strategy), how the right variant is obtained (Factory/Registry), how cross-cutting behaviour is added (Decorator/Proxy), how a tree is traversed (Composite + Visitor or Iterator), how lifecycle is expressed (State), how invocations are stored and replayed (Command + Memento for undo, Composite for macros), and how the whole is presented (Facade). Warning signs of over-combination: two patterns addressing the same variability, abstractions with one implementation, assembly/wiring code larger than the behaviour it wires, and a runtime graph nobody can draw. At codebase scale, coherence matters more than local optimality: pick one idiom per recurring problem (one way to do state machines, one way to add retries), document it, encode it in review checklists and templates, and prune dead options as deliberately as you add them. Prefer patterns that show up in names and tests over patterns that exist only in wiring.
go deeper
Say patterns often appear together, give one natural pair such as Strategy with a factory that picks the strategy, and note that too many patterns makes code hard to follow.
Name several standard pairings and explain the distinct question each pattern answers, plus warning signs like single-implementation abstractions and wiring that outweighs behaviour.
Discuss trade-off oppositions (Composite vs Visitor axes), implicit framework-generated patterns and their edge cases, and how to decide when a combination has exceeded readers' capacity.
Frame it as governance: one idiom per recurring problem, enforcement via templates/architecture tests rather than prose, spending governance budget on boundary patterns, deliberate pruning of dead abstractions, and migration strategy when competing idioms exist.
## Why combination is the normal case A real feature has several independent questions to answer at once. Each pattern answers one. So a healthy design looks like a small set of patterns, each on a different concern, rather than one pattern doing everything or six patterns fighting over the same decision. The test for a *legitimate* combination: **can you state, in one sentence each, the distinct question every pattern answers?** If two patterns answer the same question, one is redundant. ## Natural combinations and why they work - **Strategy + Factory/Registry.** Strategy says behaviour varies; something must map an input (enum, tenant, config key) to the right variant. That mapping is the factory's question, not the strategy's. - **Composite + Visitor.** Composite gives a uniform tree of parts and wholes; Visitor adds new operations over that tree without editing every node type. They trade in opposite directions: Composite makes new *node types* cheap, Visitor makes new *operations* cheap. (That opposition is the "expression problem", and choosing which axis to keep cheap is a real design decision.) - **Composite + Iterator.** Traversal order becomes an explicit, swappable concern. - **Command + Composite** — macro commands. **Command + Memento** — undo when the inverse cannot be computed. **Command + Queue/Scheduler** — deferral, retry, audit. - **State + Strategy.** A lifecycle state delegates a varying computation to an injected policy. - **Decorator/Proxy + almost anything.** Cross-cutting concerns (retry, metrics, caching, authorisation) layer over any interface without changing it. - **Facade + Adapter.** A boundary that both simplifies a subsystem and translates its interfaces; common in anti-corruption layers between a domain model and an external system. - **Observer + Mediator.** Observer decouples publisher from subscriber; a Mediator centralises the interaction rules when the pub/sub web itself becomes the complexity. - **Template Method vs Strategy** as alternatives, not partners: inheritance-based variation versus composition-based. Choosing both for the same variation is a smell. ## Signals of over-combination 1. **Two patterns, one question.** A Strategy interface *and* a Template Method hierarchy for the same variation; a Factory *and* a DI container *and* a Service Locator resolving the same dependency. 2. **Wiring exceeds behaviour.** More lines assembling the object graph than implementing domain rules. 3. **Undrawable runtime graph.** If no one can sketch which object wraps which at runtime, the design has exceeded its readers. 4. **Single-implementation abstractions.** Interfaces, factories, and registries with one entry are dead options. 5. **Naming drift.** `AbstractSingletonProxyFactoryBean`-style names indicate patterns applied to patterns rather than to problems. 6. **Debugging cost.** Time-to-locate-behaviour rises: reviewers can no longer answer "where does this actually happen?" without running it. ## Coherence at codebase scale At scale the goal shifts from "best pattern here" to "**one predictable idiom per recurring problem**", because most of the cost is in humans navigating unfamiliar code. - **Pick and document one idiom** per recurring problem: how state machines are expressed, where retries live (decorator? middleware? infrastructure?), how variants are registered, how cross-service calls are wrapped. Two good idioms are worse than one adequate one. - **Make it visible.** Names carrying the pattern role, module layout, and a short "patterns we use and why" note beat tribal knowledge. - **Encode it where it is enforced** — templates, scaffolds, review checklists, architecture tests/lint rules — not just prose. Rules nobody checks decay. - **Prune deliberately.** Removing an abstraction that never gained a second implementation is a genuine simplification; schedule it like any other maintenance. - **Watch the seams that matter most.** Patterns at module and service boundaries (adapters/ports, facades, commands/events) are expensive to change later; patterns inside a single class are cheap and need less governance. Spend the governance budget on the former. - **Prefer patterns visible in code over patterns hidden in configuration.** A decorator stack assembled in one documented factory is auditable; the same behaviour spread across annotations, framework proxies, and container config is not. ## Framework-generated patterns Much pattern usage today is implicit: dependency-injection containers act as configurable factories, ORMs return virtual proxies for lazy relations, middleware pipelines are decorator chains, message consumers are commands. Two consequences for selection: **do not hand-roll what the platform already provides**, and **understand the generated pattern's edge cases** — self-invocation bypassing a proxy, lazy loading outside a session, middleware order mattering — because those are the same trade-offs you would have owned yourself.
- Composite plus Visitor is a classic pairing. What does that pair make expensive, and when would you avoid it?It makes adding new node types expensive: every visitor must gain a method for the new type. That is the expression problem — Composite alone keeps new node types cheap, Visitor keeps new operations cheap, and you cannot have both freely. Avoid Visitor when node types churn more than operations, or when the structure is small enough that pattern matching or a simple recursive function is clearer.
- How would you audit a large codebase for pattern usage that is no longer earning its keep?Mechanically: find interfaces with exactly one implementation and no test double, factories and registries with a single entry, abstraction layers that callers routinely bypass, and decorator stacks whose layers were added but never configured differently. Cross-reference with change history — if nothing has varied there in years, the option is dead and inlining it is a real simplification.
- How do you handle a team that has adopted two competing idioms for the same problem?Decide one, document why, and migrate opportunistically rather than in a big-bang rewrite: new code uses the chosen idiom, touched code converts, and the losing idiom gets a deprecation note so nobody reintroduces it. The cost of two live idioms is paid by every reader forever; the cost of migrating is paid once and can be amortised.
Patterns combine like ingredients in a recipe: salt, acid, and heat each do a different job, and a good dish uses several. Adding three sources of salt is not sophistication — it is one job done three times.
saying these in an interview costs you the question
- Treating pattern count as a quality metric, or combining patterns for demonstration value.
- Applying two patterns to the same axis of variation (e.g. Template Method and Strategy for one decision).
- Ignoring that Composite + Visitor makes new node types expensive.
- Hand-rolling factories, proxies, or decorator chains the platform already generates.
- Believing local optimality beats codebase-wide consistency — at scale, predictability usually wins.
- Never removing abstractions; treating pattern adoption as one-way.