From Requirements to Classes
Every LLD answer starts the same way: read the requirements, pull out the nouns and verbs, and decide who owns which responsibility.
- 4 scoresRequirements, model, change, a working core
- 5 to 8Classes that usually suffice for a classic question
- 1 sentenceWhat a class must protect, or merge it away
Show what to say
“I start an LLD answer by reading the requirements twice: nouns become classes, verbs become methods, and I ask of each method who owns it. Rules live next to the data they read, I prefer composition to inheritance, and I stop adding classes when a new one has nothing to protect.”
Trade-offs
- Composition vs inheritance: a part you swap, and a hybrid that is a new part, against a tree that multiplies with every new trait.
- Composition vs dependency: composition creates and owns the part, so a test cannot swap it; a dependency is handed in, so a fake fits.
- Few classes vs a class per noun: five to eight classes with a reason each beat Manager and Factory suffixes that protect nothing.
Bring it up before they ask
Say which single class you would edit if a rule changed (students get 7 days instead of 14), because a rule written in two places is how a god class bites.
Nouns become classes, verbs become methods, and every method gets one question: who owns this? Keep each rule next to the data it reads, prefer parts you swap over family trees, and stop adding classes when a new one protects nothing.
What an LLD round scores
The interviewer is watching how you decide, not whether your code compiles in their head. Four things earn points:
- Requirements: you ask about limits and scope before you draw.
- The model: sensible nouns, honest relationships.
- Change: you name the requirement most likely to change and show the one place it lands.
- A working core: one scenario, end to end, with edge cases said out loud.
This lesson follows a lending library, from one class that does everything to classes that each own one thing.
Nouns and verbs from a prompt
Highlight the nouns (classes) and the verbs (methods).
A library lends books to members. A member may hold three books at a time, each for 14 days. Returning late costs 0.50 per day.
| In the prompt | Becomes | Why |
|---|---|---|
| library | Library | The entry point |
| books, members | Book, Member | Each has data and rules |
| lends, returns | borrow, giveBack | Verbs are methods |
| holds a book for 14 days | Loan | A link with its own data is a noun |
A noun with no data to protect and no behaviour is just a field. A day or a fee is a number.
Who owns the behaviour
In god-library-before.js one class does everything. Then students get 7 days instead of 14. The 14 lives in two places, so the email and the fee disagree. That is a god class: many reasons to change.
The fix is moving each rule next to the data it reads. In library-after.js a Loan knows its due day, a Member knows their limit, and Library only coordinates. Students are one new Member.
Rule of thumb: a method that mostly reads another class's fields belongs on that class.
// One class that knows stock, members, loan rules, fees and email.
export class Library {
constructor() {
this.books = new Map(); // isbn -> { title, copies }
this.members = new Map(); // id -> { name, loans: [{ isbn, day }] }
this.outbox = [];
}
addBook(isbn, title, copies) { this.books.set(isbn, { title, copies }); }
join(id, name) { this.members.set(id, { name, loans: [] }); }
borrow(memberId, isbn, day) {
const member = this.members.get(memberId);
const book = this.books.get(isbn);
if (!member || !book || book.copies === 0) return false;
if (member.loans.length >= 3) return false; // limit: hard-wired here
book.copies -= 1;
member.loans.push({ isbn, day });
this.outbox.push(`${member.name}: ${book.title} is due on day ${day + 14}`); // 14 days, once
return true;
}
giveBack(memberId, isbn, day) {
const member = this.members.get(memberId);
const i = member ? member.loans.findIndex((l) => l.isbn === isbn) : -1;
if (i < 0) return -1;
const [loan] = member.loans.splice(i, 1);
this.books.get(isbn).copies += 1;
return Math.max(0, day - (loan.day + 14)) * 0.5; // 14 days again, plus the fee rule
}
}Composition over inheritance
Think of a toy car with a swappable battery pack. The car has a battery. That is composition. Inheritance says "is a", and breaks fast: an ElectricCar is a Car until fillUp calls refuel() on it.
In inheritance-before.js every new trait multiplies the tree: electric, hybrid, truck, electric truck. In composition-after.js a vehicle has an energy source, and a hybrid is just a new part.
Inherit only when every subclass works anywhere the parent does (Liskov substitution).
// "An electric car is a car" reads fine until someone calls refuel().
export class Vehicle {
constructor(plate) { this.plate = plate; }
}
export class Car extends Vehicle {
refuel() { return `${this.plate}: petrol in the tank`; }
}
export class ElectricCar extends Car {
refuel() { throw new Error('electric cars have no tank'); } // breaks every caller of Car
charge() { return `${this.plate}: plugged in`; }
}
export const fillUp = (vehicles) => vehicles.map((v) => v.refuel());The relationships you draw
A class diagram is a thinking tool, so draw it in the order you think. Step through the four stages: each one pulls out a noun, a relationship that carries data, or a collaborator.
On a whiteboard, write the one class you cannot avoid, add the nouns, then ask which relationships hold data of their own.
Stage one is a single class that does everything. Each later stage pulls out a noun, then a relationship that carries data, then a collaborator the library is handed. Library composes Book, aggregates Member, Member composes Loan, Loan refers to a Book, and Library depends on a Notifier only while it sends.
- ◆ composition (owns)
- ◇ aggregation
- △ inheritance / implements (dashed)
- → association · ⇢ dependency
What each line means in code
Each line is a question about lifetime.
| Relationship | Ask | In the code |
|---|---|---|
| Association | Does A know B? | A field or argument holding B |
| Aggregation | A holds B, but B outlives A | A field filled from outside |
| Composition | A creates and owns B | new B inside A |
| Dependency | Does A use B for one call? | A parameter or a local |
relationships.js has one of each.
In an interview, composition against dependency is the one that matters. It decides who creates an object and how a test replaces it.
// The four lines you draw, as they look in code.
export class Notifier {
send(to, text) { return `to ${to}: ${text}`; }
}
export class Book {
constructor(isbn, title) { this.isbn = isbn; this.title = title; }
}
export class Member {
constructor(id, name) { this.id = id; this.name = name; }
}
export class Loan {
constructor(book, member, day) { this.book = book; this.member = member; this.day = day; } // association: knows both, owns neither
}
export class Library {
constructor() { this.books = []; this.members = []; this.loans = []; }
addBook(isbn, title) { // composition: created here, lives and dies with this library
const book = new Book(isbn, title);
this.books.push(book);
return book;
}
join(member) { this.members.push(member); } // aggregation: made elsewhere, outlives this library
lend(member, book, day, notifier) { // dependency: the notifier is only used during this call
const loan = new Loan(book, member, day);
this.loans.push(loan);
return notifier.send(member.name, `borrowed ${book.title}`);
}
}Encapsulation and tell, don't ask
In library-after.js, member.loans.push(loan) reaches into a member and decides for it. That is asking, and the limit rule now lives in whoever calls.
reach-in-before.js shows the cost: the desk checks the limit, the kiosk forgets, and one member holds two books on a limit of one. In tell-after.js the member hides its list and exposes borrow, so no caller can skip the rule.
Rule of thumb: if two callers need the same if on an object's fields, move the if into the object.
// The rule lives in the callers, so every caller has to remember it.
export class Member {
constructor(limit = 3) { this.limit = limit; this.loans = []; }
}
export function borrowAtDesk(member, isbn) {
if (member.loans.length >= member.limit) return false;
member.loans.push(isbn);
return true;
}
export function borrowAtKiosk(member, isbn) {
member.loans.push(isbn); // someone forgot the limit check, and nothing stops them
return true;
}The too-many-classes trap
A class earns its place when it has state to protect, behaviour that would be duplicated, or a requirement that varies there. Otherwise it is a function or a field.
Nothing there protects anything. Compare Loan.feeAt(day): one method, on the one class that knows the due day. Five to eight classes usually suffice, as in the parking lot question.
LoanFeeCalculatorFactory -> LoanFeeCalculator -> FeeResult
BookManager, MemberManager, LoanManager, LibraryManagerTry it
Change Ana's limit, make Sam a 14-day member, or move a return day, and run it. Each change touches one number in one place.
// Change a limit, a loan period or a day, then press Run.
const lib = new Library();
lib.addBook('b1', 'Dune', 1);
lib.addBook('b2', 'Emma', 2);
lib.join(new Member('ana', 'Ana', 1)); // limit 1; try 2
lib.join(new Member('sam', 'Sam', 3, 7)); // a student: 7 days
console.log(lib.borrow('ana', 'b1', 0) !== null); // true
console.log(lib.borrow('ana', 'b2', 0) !== null); // false: Ana is at her limit
console.log(lib.borrow('sam', 'b2', 0).dueDay); // 7
console.log(lib.giveBack('ana', 'b1', 20)); // 3: six days late at 0.5
console.log(lib.giveBack('sam', 'b2', 10)); // 1.5: three days lateFollow-up questions
How do you decide what becomes a class?
It has data to protect or behaviour that would be duplicated. A link with its own data, like a loan, counts. The rest are fields.
When is inheritance the right choice?
When every subclass can stand in for the parent and the tree stays shallow. If you override a method to throw, use a part instead.
Check yourself
Question 1 of 3Score 2 out of 3 or better to mark this page complete.
Code it yourself
Write the class in the browser editor, in JavaScript or Python. Each exercise runs against hidden tests, and the step-by-step solution unlocks after a wrong submission.
- Tic-Tac-ToeModel an N by N tic-tac-toe game so that a Game coordinates turns and results while a Board owns the grid, and every move is checked in constant time.Easy
- Parking LotBuild a multi-floor parking lot whose spot choice and pricing are separate, swappable policies you can change while it is running.Medium