LLD INTERVIEW WORKFLOW / OBJECT DESIGN BRIEF

Assign responsibilities

Step 3: assign behavior to entities.

IntermediatePhase 10 / Topic 3 of 8ResponsibilitiesCollaborationsExtensibility
01

Overview

Step 3: assign behavior to entities. Use the Information Expert principle (give a responsibility to the class that has the data needed), keep each class cohesive (SRP), and make services thin orchestrators for use cases that span several entities. The goal is a rich domain model where rules live next to the data they protect.

For each use case step, ask 'who knows?' and 'who should enforce this rule?'. Show knows its seats, so Show decides whether seats can be held. Hold knows its expiry, so it answers isExpired(). BookingService coordinates holding, paying, and confirming but does not contain seat rules.

Delegating in a team

You ask the person who has the information to make the decision: the accountant approves budgets, the designer approves layouts. The project manager coordinates but does not do everyone's job.

02

When to use it

  • After listing entities, before coding.
  • When a service class is growing too large.
  • When the same rule appears in multiple places.
03

Where it shows up in interviews

Rich domain model

Recognize it when: rules scattered in services.

  • Design a movie ticket system
  • Design a library system
  • Design Splitwise
04

Where it is used in real software

GRASP principles

Craig Larman's Information Expert, Creator, Controller, Low Coupling, and High Cohesion guide responsibility assignment.

CRC cards

Teams role-play use cases, passing responsibilities between cards.

Anemic domain model critique

Martin Fowler warns against entities that are only data with logic in services.

05

Key terms

Information Expert
Assign to the class with the needed information.
Creator
The class that contains or aggregates B should create B.
Controller
A service handling a use case's system events.
Cohesion
How focused a class's responsibilities are.
06

How it works, step by step

  1. 1
    List use case steps

    hold seats, compute price, charge, confirm.

  2. 2
    For each step ask 'who knows?'

    Show knows seats; PricingStrategy knows prices.

  3. 3
    Assign rule enforcement to the owner

    Show.hold() rejects unavailable seats.

  4. 4
    Keep orchestration in services

    BookingService calls Show, Payment, Hold.

  5. 5
    Check cohesion

    Each class's methods relate to one concept.

07

Responsibility assignment

Movie ticket use cases

Step 1 / 6
ResponsibilityAssigned toWhy
Are these seats available?ShowOwns ShowSeats
Mark seats held/bookedShowProtects seat invariants
Is this hold expired?HoldKnows its expiry
Price for a seatPricingStrategyVaries independently
Charge the customerPaymentGatewayExternal system
Run the booking flowBookingServiceCoordinates several objects

NOWResponsibility: Are these seats available? | Assigned to: Show | Why: Owns ShowSeats

BookingService stays short because each rule lives with its expert.

08

Implementation

class Hold {  constructor(readonly id: string, readonly showId: string, readonly userId: string, readonly seatIds: string[], readonly expiresAt: number) {}  isExpired(now: number) { return now >= this.expiresAt; }                 // expert: knows its expiry} class Show {  private status = new Map<string, "available" | "held" | "booked">();  constructor(readonly id: string, seatIds: string[]) { seatIds.forEach((s) => this.status.set(s, "available")); }   hold(seatIds: string[]) {                                                 // expert: owns seat state    if (!seatIds.every((s) => this.status.get(s) === "available")) throw new Error("Seat unavailable");    seatIds.forEach((s) => this.status.set(s, "held"));  }  book(seatIds: string[]) {    if (!seatIds.every((s) => this.status.get(s) === "held")) throw new Error("Seats not held");    seatIds.forEach((s) => this.status.set(s, "booked"));  }  release(seatIds: string[]) { seatIds.forEach((s) => this.status.get(s) === "held" && this.status.set(s, "available")); }} class BookingService {                                                      // controller: orchestrates only  constructor(private shows: Map<string, Show>, private pay: (u: string, c: number) => Promise<boolean>, private now = () => Date.now()) {}  async confirm(hold: Hold, amountCents: number) {    const show = this.shows.get(hold.showId)!;    if (hold.isExpired(this.now())) { show.release(hold.seatIds); throw new Error("Hold expired"); }    if (!(await this.pay(hold.userId, amountCents))) { show.release(hold.seatIds); throw new Error("Payment failed"); }    show.book(hold.seatIds);  }}
09

Complexity and performance

Service sizeSmall

Orchestration only.

Rules per entityThose it owns

High cohesion.

10

Trade-offs

Rich model vs transaction scripts

Rich models protect invariants and scale with complexity; transaction scripts are simpler for trivial CRUD.

Coupling

Placing logic with the expert may add dependencies; watch for cycles.

11

Variants and related techniques

Domain services

For rules that belong to no single entity (transfer between accounts).

Policies

Strategy objects for rules that vary (pricing, refunds).

12

Common mistakes

  • Anemic entities with all rules in services.

    Fix: Move rules to the entity that owns the data.

  • God service.

    Fix: Split by use case and delegate to entities and policies.

13

Interview questions

How do you decide which class gets a method?

Apply Information Expert: the class that has the data needed to fulfill the responsibility should own it, as long as that keeps cohesion high and coupling low. Cross-entity workflows go to a thin service.

What is an anemic domain model?

Entities that are just data with getters and setters, while all business logic lives in services. It scatters rules and makes invariants easy to break.

14

Practice problems

ProblemDifficultyWhat it trains
Assign responsibilities for SplitwiseMediumExpert vs service.
Refactor an anemic Order modelMediumMove rules into entities.