Problem 4.4 — Library Management
In one line
Entities, borrow/return flows, fine calculation and search — the 'clean CRUD with rules' LLD problem.
Key ideas
- 01
In plain words: a library has books, and each book might have several physical copies. A member borrows one copy at a time, must return it within a deadline, and pays a fine if they're late. The trick most people miss: 'Harry Potter' the title and 'this specific copy on the shelf' are two different things.
- 02
Entities: Book (ISBN, title, author), BookCopy (id, condition), Member, Loan, FineCalculator.
- 03
Loan lifecycle: borrow (copy → checked out) → return (fine if overdue) → renew.
- 04
One BookCopy can be on one active Loan — the invariant to keep thread-safe.
- 05
Fine: strategy by member type (student/staff) and book category, per-day rate.
- 06
Search: by title/author/ISBN — index-friendly; keep the search behind a port (later: Elastic/DB full-text).
- 07
Reservations/holds add the next LLD layer: queue per book, notify on availability (Observer).
- 08
Extensibility: new fine rule, new search engine, new media type (DVD) each hits one seam.
Java / Spring map
- →
BorrowService with @Transactional loan creation; LoanRepository with a unique partial index on active loans.
Code & diagrams
Book vs BookCopy, a fine strategy, and the borrow flow that protects the 'one active loan per copy' rule.
public record Book(String isbn, String title, String author) {}
public final class BookCopy {
private final String copyId;
private final String isbn;
private boolean onLoan = false;
public BookCopy(String copyId, String isbn) { this.copyId = copyId; this.isbn = isbn; }
public synchronized boolean checkout() { // atomic claim, same idea as ParkingSpot
if (onLoan) return false;
onLoan = true;
return true;
}
public synchronized void checkin() { onLoan = false; }
public String copyId() { return copyId; }
}
public interface FinePolicy { Money finePerDay(); Duration loanPeriod(); }
public record StudentFinePolicy() implements FinePolicy {
public Money finePerDay() { return new Money(1000, "INR"); } // ₹10/day
public Duration loanPeriod() { return Duration.ofDays(14); }
}
public final class Loan {
private final BookCopy copy;
private final Instant dueAt;
private Instant returnedAt;
private final FinePolicy policy;
public Loan(BookCopy copy, Instant borrowedAt, FinePolicy policy) {
this.copy = copy; this.policy = policy;
this.dueAt = borrowedAt.plus(policy.loanPeriod());
}
public Money returnBook(Instant now) { // "tell, don't ask": Loan computes its own fine
this.returnedAt = now;
copy.checkin();
long lateDays = Math.max(0, Duration.between(dueAt, now).toDays());
return new Money(policy.finePerDay().minor() * lateDays, "INR");
}
}
public final class LibraryService {
public Loan borrow(BookCopy copy, FinePolicy policy, Instant now) {
if (!copy.checkout()) throw new IllegalStateException("copy already on loan: " + copy.copyId());
return new Loan(copy, now, policy);
}
}Explain without notes
How do you prevent two members from borrowing the same copy simultaneously in a real (multi-instance) app?
Practice
Add Reservation + waitlist with queue; add 'lost book' flow where the borrower pays replacement cost.
Trade-offs
- ↔
Fines config in DB vs code: DB = easy ops change, code = compile-time safety. Pick one and justify.
Completion checklist
I can model Book/BookCopy/Member/Loan with correct multiplicities and invariants, and can explain why Book and BookCopy are two different classes.