Design a Library Management System (LLD)
Separate the title from the copy, give each copy a small state machine, make fines a strategy, and make the last-copy borrow atomic.
- 4 statesavailable, loaned, held, lost per copy
- 3 daysexample hold deadline for the next in line
- 1 atomic stepcheck available and set loaned together
Show what to say
“I'd separate the title, Book, from the physical copy, BookItem. A copy is a small state machine: available, loaned, held or lost. Fines are a strategy chosen by member type, notices are observers, and borrowing the last copy is one atomic check-and-set. A returned copy goes to the reservation queue before it can become available.”
Trade-offs
- Member type as data vs a subclass each: no hierarchy to grow, but an odd rule needs code.
- One lock vs a lock per title: simple and correct now, against contention across a big catalogue.
- Hold with a deadline vs no hold: fair to the queue, but it needs a daily sweep and a notice.
Bring it up before they ask
The last-copy race: if the code checks available and then writes loaned as two steps, two members both get the copy.
Separate the title (a Book people search and reserve) from the copy (a BookItem that is loaned, held or lost). Give each copy a small state machine, make fines a swappable strategy, and make "check the copy is free, then loan it" one atomic step.
Functional
- Add books with several copies; search by title, author or ISBN
- Members borrow and return copies
- A loan limit and a loan period that depend on the member type
- Reserve a title with no free copy; the first in line gets the next copy
- Renew a loan, within limits
- A fine for late returns
- Notify members: ready for pickup, overdue
Non-functional
- Two members never get the same copy
- The queue is first come, first served
- Fine rules change without editing the library
- Notifications can be added without editing the library
- Deterministic tests: time is passed in as a day number
Out of scope
- Payments for fines
- Shelf locations and barcode hardware
- Several branches (a follow-up)
A low-level design (LLD) question: the nouns are easy, the rules are the test.
New to this? Read State machines and observers and Swap behaviour first.
Clarifying questions and interface
Read more · 174 words
- Are there different kinds of member, with different limits and loan periods?
- How are fines worked out, and is there a cap?
- Can a member reserve a title? How long is a hold kept?
- Can loans be renewed, and how often?
- One branch or many? Digital loans too?
| Call | Returns | Notes |
|---|---|---|
borrow(member, isbn, day) | A loan, or a reason | LIMIT_REACHED, NO_COPY, UNKNOWN |
returnItem(loan, day) | The fine in cents | Hands the copy to the queue first |
reserve(member, isbn) | Place in the queue | Refused while a copy is free |
renew(loan, day) | The new due day | Refused if overdue, waited for, or renewed twice |
search(text) | ISBNs | Title or author contains it, or the ISBN matches |
sweep(day) | Overdue notices once, lapsed holds pass on |
What it remembers
books: ISBN → title, author, its copies and its queue of waiting members.- Each copy: state, and who it is held for and until when.
loans: copy, member, due day, renewals, open or closed.members: type, which gives the limit and the loan period.
Build the design in stages
Step through: title and copy, then members and loans, then the copy's lifecycle, then fines, then listeners.
Read more · 105 words
Green marks what the latest stage added.
Patterns, and when to skip them. State keeps the legal moves in one table. Strategy turns days late into cents, one policy per member type, and a Decorator wraps a policy in a cap or a grace period without editing it. Observer sends "ready" and "overdue" to email or SMS, so the library never knows who listens.
Skip the rest. A subclass per member type is data pretending to be code, so use a small record of limits. A FineCalculatorFactory for two policies, or a LibraryManager that does everything, loses points. Keep one Library as the entry point.
A Book is a title and owns its copies (BookItem) and its waiting queue. A copy moves through ItemState. Library is the entry point: it creates Loans, asks a FinePolicy chosen by member type for the fine, and publishes events to listeners.
- ◆ composition (owns)
- ◇ aggregation
- △ inheritance / implements (dashed)
- → association · ⇢ dependency
Borrow and return, step by step
Pick a branch: a normal borrow, the limit, a title with every copy out.
- Client
- Service
- Database
- Worker
- Pub/Sub
Text description
- Member → Library: borrow(ann, 222, day 10)
- Library → Book: held for ann, else any AVAILABLE copy
- Book replies to Library: copy 222#2
- Library → BookItem: moveTo(LOANED)
- Library replies to Member: loan L1
- Library sends (async) to Listeners: borrowed
A copy's life and the last copy
A copy is always in one state.
Read more · 196 words
Only the moves in the table are legal, and anything else is a bug that should fail loudly.
| From | Event | To |
|---|---|---|
| AVAILABLE | Member borrows it | LOANED |
| LOANED | Returned, nobody waiting | AVAILABLE |
| LOANED | Returned, someone waiting | HELD |
| HELD | Waiting member borrows it | LOANED |
| HELD | Hold lapses | AVAILABLE, or HELD for the next |
| LOANED | Reported lost | LOST |
Now the return diagram: pick a branch with and without a queue. A returned copy goes to the queue before it can become available. That one rule keeps the queue fair.
The last copy. Two members borrow the last copy in the same instant. If the code checks "is it free?" and then sets "loaned" as two steps, both pass the check.
- Make it one step. One lock around the change is the honest first answer. In a database it is one conditional update,
SET state = 'LOANED' WHERE id = ? AND state = 'AVAILABLE', then checking that a row changed. - Check the member limit in the same step, or two quick borrows slip past it.
- Tell listeners after releasing the lock, so a slow email never blocks a borrow.
- Scale up later with a lock per title, not one for the whole library.
- Client
- Service
- Worker
- Database
- Pub/Sub
Text description
- Member → Library: returnItem(L1, day 18)
- Library → FinePolicy: fine(18 - 14 = 4 days late)
- FinePolicy replies to Library: 100 cents (4 x 25)
- Library → BookItem: moveTo(AVAILABLE)
- Library replies to Member: fine 100
Step-by-step answer
Five moves: clarify (member types, fines, reservations, renewals), split title and copy (search and queues on the title, state on the copy), draw the copy's four states with a table of legal moves, pull out what varies (fines as a policy, notices as events), walk the race (the last copy, then the queue).
Read more · 173 words
Nouns
Book, BookItem, Member, Loan, Reservation queue, FinePolicy. A reservation is just a place in the book's queue, so it needs no class of its own.
Borrow, in order
Check the limit. Prefer a copy held for this member, otherwise any available one. Move it to LOANED, create the loan with a due day from the member type.
Return hands over before it frees
The copy goes to the first waiting member, or becomes available.
#release(item, day) { const next = this.books.get(item.isbn).queue.shift(); if (next === undefined) { item.moveTo('AVAILABLE'); return; } item.moveTo('HELD'); item.heldFor = next; item.holdUntil = day + HOLD_DAYS; }Fines are a policy
fine(daysLate)for each member type. A cap wraps a per-day rate. Money is integer cents.Daily sweep
A sweep gets the day as an argument. It sends each overdue notice once and passes lapsed holds on, with no clock and no timer thread.
Tests
Test the queue order, a hold lapsing, each renewal refusal, every fine, and eight threads racing for one copy.
Implementation and code it yourself
The same design in JavaScript, Java or Python.
Read more · 76 words
Java and Python guard every change with a real lock and notify listeners after releasing it.
moveTothrows on an illegal move, so a logic error surfaces in the test and not as a wrong count.- The race test starts eight threads on one copy, 100 times: there is always exactly one winner.
Try the Library System exercise: borrowing limits, a fair reservation queue with holds, renewals and a fine policy, all checked by exact numbers.
// A copy moves through a small state machine. Anything not listed here is a bug, and fails loudly.
const NEXT = {
AVAILABLE: ['LOANED'],
LOANED: ['AVAILABLE', 'HELD', 'LOST'],
HELD: ['LOANED', 'AVAILABLE', 'HELD'], // HELD -> HELD: the hold lapsed and passes to the next in line
LOST: [],
};
export class BookItem { // one physical copy
constructor(id, isbn) {
Object.assign(this, { id, isbn });
this.state = 'AVAILABLE';
this.heldFor = null; // member id while HELD
this.holdUntil = null;
}
moveTo(state) {
if (!NEXT[this.state].includes(state)) throw new Error(`${this.state} -> ${state} is not allowed`);
this.state = state;
}
}
export class Book { // the title: one record, many copies
constructor(isbn, title, author) {
Object.assign(this, { isbn, title, author });
this.items = [];
this.queue = []; // member ids waiting, oldest first
}
}
// What differs by kind of member is data, not a subclass.
export const MemberType = {
STANDARD: { maxLoans: 3, loanDays: 14 },
PREMIUM: { maxLoans: 5, loanDays: 21 },
};Failure modes and trade-offs
Read more · 161 words
| What breaks | Effect | Mitigation |
|---|---|---|
| Check then write | One copy loaned twice | One atomic step |
| Returned copy goes to AVAILABLE first | A walk-in jumps the queue | Release to the queue first |
| Hold never expires | An absent member blocks others | Deadline, then pass on |
| Overdue notice every sweep | Members get spammed | A notified flag per loan |
| Fine as a float | Cents drift | Integer cents |
Trade-offs
| Choice | Pros | Cons |
|---|---|---|
| One lock vs lock per title | Simple and correct | Contention at scale |
| Member type as data vs subclass | No hierarchy to grow | Odd rules need code |
| Hold with a deadline vs no hold | Fair to the queue | A sweep and a notice |
| Queue on the book vs Reservation class | Less code | No place for per-reservation data |
Follow-up questions
How would you add another fine rule?
Write a new FinePolicy and register it for a member type. A weekend discount wraps an existing policy as a decorator. The library does not change.
How would you support many branches?
A Branch owns copies, and the title and the queue stay global. A return at another branch is a transfer, and a hold names the branch to collect from.
How do you handle a lost book?
Move the copy to LOST, close the loan, and charge a replacement fee from a policy. LOST has no way out, so it can never be loaned again.
How would you make search fast?
Keep an from words in titles and authors to ISBNs, and a map for exact ISBNs. A search service takes over at scale.
How would you send reminders three days before the due date?
The daily sweep emits a dueSoon event for loans due within three days. Listeners decide to send email or SMS.
Check yourself
Question 1 of 3Score 2 out of 3 or better to mark this page complete.