Overview
Step 6: apply design patterns where the requirements create a need, and name the reason. Patterns solve specific forces: Strategy for interchangeable algorithms (pricing), State for mode-dependent behavior (seat or machine states), Observer for notifications, Factory for creation that varies, Decorator for add-ons, Chain of Responsibility for pipelines, Command for undo and queues.
Interviewers reward intentional use ('pricing varies by seat type and weekend, so PricingStrategy') and penalize pattern-stuffing (a Singleton, Factory, and Visitor with no requirement behind them). A good rule: every pattern must map to a requirement or a likely change you discussed.
A carpenter reaches for a saw to cut and a hammer to nail. Using every tool on every job is a sign of inexperience; choosing the right tool for the task is skill.
When to use it
- While designing the class diagram and code.
- When a requirement introduces variation or mode-dependent behavior.
- When the interviewer asks 'how would you extend this?'.
Where it shows up in interviews
Recognize it when: requirement implies variation, modes, or events.
- Design a movie ticket system
- Design a vending machine
- Design a notification service
Where it is used in real software
Design Patterns (1994) catalogued 23 patterns, each with intent, forces, and consequences.
Spring (proxies, template methods, factories) and React (composition, observer-like hooks) are built from patterns.
Senior reviewers question patterns that add indirection without a clear need.
Key terms
- Force
- A requirement or change pressure a pattern resolves.
- Intent
- The problem a pattern is meant to solve.
- Consequences
- Trade-offs introduced by the pattern.
- Pattern-stuffing
- Using patterns without a matching need.
How it works, step by step
- 1List variation points from requirements
Pricing rules, payment providers, seat states.
- 2Match each to a pattern
Strategy, Adapter, State.
- 3State the reason out loud
'Because pricing changes by season.'
- 4Keep the rest simple
No pattern where a function or class suffices.
- 5Mention alternatives
'An enum table would also work for simple states.'
Patterns in the movie ticket system
Each tied to a requirement
| Requirement / force | Pattern | Where |
|---|---|---|
| Prices vary by seat type, day, promotions | Strategy | PricingStrategy |
| Multiple payment providers | Adapter | StripeGateway, RazorpayGateway |
| Seat lifecycle available/held/booked | State (or enum transitions) | ShowSeat |
| Email/SMS on booking | Observer | BookingEvents listeners |
| Refund rules by timing | Strategy | RefundPolicy |
| No need | Singleton, Visitor | Not used |
NOWRequirement / force: Prices vary by seat type, day, promotions | Pattern: Strategy | Where: PricingStrategy
Five patterns, each justified; unneeded patterns are deliberately omitted.
Implementation
// Strategy: pricing varies independently of booking logicinterface PricingStrategy { price(seatType: "regular" | "premium", showStart: Date): number }const standard: PricingStrategy = { price: (t) => (t === "premium" ? 1500 : 1000) };const weekend: PricingStrategy = { price: (t, d) => standard.price(t, d) * ([0, 6].includes(d.getDay()) ? 1.2 : 1),}; // Observer: booking events notify interested parties without couplingtype BookingListener = (b: { id: string; userId: string }) => void;class BookingEvents { private listeners: BookingListener[] = []; subscribe(l: BookingListener) { this.listeners.push(l); } confirmed(b: { id: string; userId: string }) { this.listeners.forEach((l) => l(b)); }} // Adapter: providers behind one interfaceinterface PaymentGateway { charge(userId: string, cents: number): Promise<boolean> }class StripeAdapter implements PaymentGateway { constructor(private stripe: { paymentIntents: { create(o: object): Promise<{ status: string }> } }) {} async charge(userId: string, cents: number) { const intent = await this.stripe.paymentIntents.create({ amount: cents, currency: "usd", customer: userId, confirm: true }); return intent.status === "succeeded"; }}Complexity and performance
Quality over quantity.
Justify it.
Trade-offs
Patterns add seams for change but also indirection; apply them where change is expected.
Lambdas, enums, and higher-order functions often replace class-heavy patterns.
Variants and related techniques
Start simple; introduce a pattern when the second variant appears.
Layers, ports and adapters, and event-driven designs at a larger scale.
Common mistakes
- Naming patterns without explaining the force.
Fix: Always say which requirement the pattern serves.
- Singleton for services.
Fix: Use dependency injection with a single instance.
- Using many patterns to impress.
Fix: Fewer, well-justified patterns score higher.
Interview questions
Which patterns would you use in a movie booking system and why?
Strategy for pricing and refund rules that vary, Adapter for payment providers, State or explicit transitions for seat lifecycle, and Observer for notifications, each tied to a stated requirement. I would avoid Singleton and use DI instead.
How do you avoid over-engineering with patterns?
Introduce a pattern only for a concrete force: known variation, mode-dependent behavior, or decoupling needs. Otherwise keep code direct and mention how a pattern could be added later.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Choose patterns for a food delivery app | Medium | Justification. |
| Identify over-engineering in a sample design | Medium | Simplification. |