LLD INTERVIEW WORKFLOW / OBJECT DESIGN BRIEF

Apply patterns intentionally

Step 6: apply design patterns where the requirements create a need, and name the reason.

IntermediatePhase 10 / Topic 6 of 8ResponsibilitiesCollaborationsExtensibility
01

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 toolbox

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.

02

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?'.
03

Where it shows up in interviews

Pattern selection

Recognize it when: requirement implies variation, modes, or events.

  • Design a movie ticket system
  • Design a vending machine
  • Design a notification service
04

Where it is used in real software

Gang of Four

Design Patterns (1994) catalogued 23 patterns, each with intent, forces, and consequences.

Frameworks

Spring (proxies, template methods, factories) and React (composition, observer-like hooks) are built from patterns.

Code review culture

Senior reviewers question patterns that add indirection without a clear need.

05

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.
06

How it works, step by step

  1. 1
    List variation points from requirements

    Pricing rules, payment providers, seat states.

  2. 2
    Match each to a pattern

    Strategy, Adapter, State.

  3. 3
    State the reason out loud

    'Because pricing changes by season.'

  4. 4
    Keep the rest simple

    No pattern where a function or class suffices.

  5. 5
    Mention alternatives

    'An enum table would also work for simple states.'

07

Patterns in the movie ticket system

Each tied to a requirement

Step 1 / 6
Requirement / forcePatternWhere
Prices vary by seat type, day, promotionsStrategyPricingStrategy
Multiple payment providersAdapterStripeGateway, RazorpayGateway
Seat lifecycle available/held/bookedState (or enum transitions)ShowSeat
Email/SMS on bookingObserverBookingEvents listeners
Refund rules by timingStrategyRefundPolicy
No needSingleton, VisitorNot used

NOWRequirement / force: Prices vary by seat type, day, promotions | Pattern: Strategy | Where: PricingStrategy

Five patterns, each justified; unneeded patterns are deliberately omitted.

08

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";  }}
09

Complexity and performance

Patterns per design2-5 justified

Quality over quantity.

Indirection cost1 interface each

Justify it.

10

Trade-offs

Extensibility vs simplicity

Patterns add seams for change but also indirection; apply them where change is expected.

Pattern vs language feature

Lambdas, enums, and higher-order functions often replace class-heavy patterns.

11

Variants and related techniques

Refactoring to patterns

Start simple; introduce a pattern when the second variant appears.

Architectural patterns

Layers, ports and adapters, and event-driven designs at a larger scale.

12

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.

13

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.

14

Practice problems

ProblemDifficultyWhat it trains
Choose patterns for a food delivery appMediumJustification.
Identify over-engineering in a sample designMediumSimplification.