BackendMedium35 min

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.

Say it in 30 seconds
  • 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.

0 of 0 sections ticked
30-second answer

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?
CallReturnsNotes
borrow(member, isbn, day)A loan, or a reasonLIMIT_REACHED, NO_COPY, UNKNOWN
returnItem(loan, day)The fine in centsHands the copy to the queue first
reserve(member, isbn)Place in the queueRefused while a copy is free
renew(loan, day)The new due dayRefused if overdue, waited for, or renewed twice
search(text)ISBNsTitle 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.

Stage 1 of 5

A title and its copies are different things

A Book is the title, with an ISBN. Each physical copy is a BookItem. Searching is about books, borrowing is about one copy.

Library management class diagramA 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.Bookisbn, title, authorqueue: memberId[]search by title or authorBookItemidstateheldFor, holdUntilmoveTo(state)1..*

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.

Press Play to watch it run · 6 steps

The library checks the member's limit, looks for a copy held for this member and then for a free one, moves the copy to LOANED in one step, and creates the loan. If every copy is out, the member is told so and can join the queue.

Borrow a bookThe library checks the member's limit, looks for a copy held for this member and then for a free one, moves the copy to LOANED in one step, and creates the loan. If every copy is out, the member is told so and can join the queue.borrow(ann, 222, day 10)lock taken: one change at a timealt [copy available]held for ann, else any AVAILABLE copycopy 222#2moveTo(LOANED)loan due day 24 (14 days, standard member)loan L1borrowed
  • Client
  • Service
  • Database
  • Worker
  • Pub/Sub
Text description
  1. Member → Library: borrow(ann, 222, day 10)
  2. Library → Book: held for ann, else any AVAILABLE copy
  3. Book replies to Library: copy 222#2
  4. Library → BookItem: moveTo(LOANED)
  5. Library replies to Member: loan L1
  6. 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.

FromEventTo
AVAILABLEMember borrows itLOANED
LOANEDReturned, nobody waitingAVAILABLE
LOANEDReturned, someone waitingHELD
HELDWaiting member borrows itLOANED
HELDHold lapsesAVAILABLE, or HELD for the next
LOANEDReported lostLOST

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.

Press Play to watch it run · 5 steps

The library closes the loan and prices any lateness with the member's fine policy. Then the copy goes to the first member waiting, who is notified, or becomes available if nobody waits.

Return a bookThe library closes the loan and prices any lateness with the member's fine policy. Then the copy goes to the first member waiting, who is notified, or becomes available if nobody waits.returnItem(L1, day 18)fine(18 - 14 = 4 days late)100 cents (4 x 25)alt [nobody waiting]moveTo(AVAILABLE)fine 100
  • Client
  • Service
  • Worker
  • Database
  • Pub/Sub
Text description
  1. Member → Library: returnItem(L1, day 18)
  2. Library → FinePolicy: fine(18 - 14 = 4 days late)
  3. FinePolicy replies to Library: 100 cents (4 x 25)
  4. Library → BookItem: moveTo(AVAILABLE)
  5. 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
  1. 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.

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

  3. 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;
    }
  4. Fines are a policy

    fine(daysLate) for each member type. A cap wraps a per-day rate. Money is integer cents.

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

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

  • moveTo throws 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 breaksEffectMitigation
Check then writeOne copy loaned twiceOne atomic step
Returned copy goes to AVAILABLE firstA walk-in jumps the queueRelease to the queue first
Hold never expiresAn absent member blocks othersDeadline, then pass on
Overdue notice every sweepMembers get spammedA notified flag per loan
Fine as a floatCents driftInteger cents

Trade-offs

ChoiceProsCons
One lock vs lock per titleSimple and correctContention at scale
Member type as data vs subclassNo hierarchy to growOdd rules need code
Hold with a deadline vs no holdFair to the queueA sweep and a notice
Queue on the book vs Reservation classLess codeNo 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 3

Score 2 out of 3 or better to mark this page complete.

1. Why model Book and BookItem as two classes?