"Design a library management system."
Books, members, due dates — sounds like the simplest problem in this course. It stays simple exactly until two members try to reserve the last copy of the same book at the same instant, or the library adds SMS reminders alongside email ones. Let's design it so both of those land cleanly.
Clarifying questions
- Can the library hold multiple physical copies of the same title?
- Is there a borrowing limit per member?
- How are overdue fines calculated — flat fee, or per day late?
- Should members be able to reserve a book that's currently out?
Book vs BookCopy — the first insight
A library owns five copies of the same novel. They share the same title, author and ISBN — but each one has its own, independent availability state (one might be checked out, one damaged, three free). One shared metadata record, many independent physical states — this is exactly why the design needs two classes, not one.
Reminders: a Strategy
"Email or SMS reminders" is the same signal you've now seen for payments and pricing — interchangeable channels, likely to grow:
class NotificationStrategy:
def notify(self, member, message): raise NotImplementedError
class EmailNotification(NotificationStrategy):
def notify(self, member, message):
print(f"Emailing {member.email}: {message}")
class SMSNotification(NotificationStrategy):
def notify(self, member, message):
print(f"Texting {member.phone}: {message}")Data access: a Repository
Keep the catalog's lookups behind a simple interface, so business logic never imports raw database/SQL code directly:
class BookRepository:
def find_by_id(self, book_id): ...
def find_available_copy(self, book_id): ...
def save_copy(self, copy): ...Putting it together
from datetime import datetime, timedelta
class BookCopy:
def __init__(self, copy_id, book_id):
self.copy_id = copy_id
self.book_id = book_id
self.available = True
self.lock = threading.Lock()
class Loan:
def __init__(self, member, copy: BookCopy, days_allowed=14):
self.member = member
self.copy = copy
self.issued_at = datetime.now()
self.due_at = self.issued_at + timedelta(days=days_allowed)
self.returned_at = None
def is_overdue(self):
return datetime.now() > self.due_at and self.returned_at is None
class PenaltyStrategy:
def calculate(self, loan: Loan) -> float: raise NotImplementedError
class PerDayPenalty(PenaltyStrategy):
def calculate(self, loan: Loan) -> float:
if not loan.is_overdue():
return 0
days_late = (datetime.now() - loan.due_at).days
return days_late * 5
class Library:
def __init__(self, repo: BookRepository, penalty: PenaltyStrategy,
notifier: NotificationStrategy):
self.repo = repo
self.penalty = penalty
self.notifier = notifier
def borrow(self, member, book_id) -> Loan:
copy = self.repo.find_available_copy(book_id)
if not copy:
raise Exception("No copies available")
with copy.lock: # same lock-based fix as Parking Lot
if not copy.available:
raise Exception("Copy just taken — try again")
copy.available = False
return Loan(member, copy)
def return_book(self, loan: Loan) -> float:
loan.returned_at = datetime.now()
loan.copy.available = True
fine = self.penalty.calculate(loan)
if fine > 0:
self.notifier.notify(loan.member, f"Fine due: ₹{fine}")
return fineSequence: borrowing a book
The same race condition, new domain
What if the interviewer changes this?
BookCopy keeps a small queue of waiting members; on return, notify the first one via the existing NotificationStrategy — no new notification mechanism needed.borrow(): count the member's active loans before issuing a new one. Doesn't touch PenaltyStrategy or NotificationStrategy at all — a sign the responsibilities were split well.Common mistakes
- Modeling only one `Book` class and losing the ability to track multiple copies' independent states.
- Hard-coding one fine formula instead of a swappable `PenaltyStrategy`.
- Forgetting the same concurrency race condition seen in the Parking Lot lesson.
- Mixing database access code directly into `Library` instead of behind a `BookRepository`.
Quick recap
| Class / pattern | Job |
|---|---|
| Book / BookCopy | Shared metadata vs each copy's independent state. |
| Loan | One borrowing record: issued, due, returned. |
| PenaltyStrategy | Swappable overdue-fine calculation. |
| NotificationStrategy | Swappable reminder channel — email, SMS, later push. |
| BookRepository | Hides the data store behind simple find/save methods. |
One thing to remember
One title, many copies, each with its own state — model the shared part and the individual part as two separate classes, and the rest of the design (loans, races, reservations) falls into place naturally.
Practice Zone
Four MCQs, then two reasoning questions.
Why does a library system usually need both a Book (metadata) class and a separate BookCopy class, instead of just one Book class?
Asked in


Why is 'send a due-date reminder by email or SMS' a Strategy signal rather than an if/else inside Loan?
Asked in

What job does a BookRepository own that Library itself shouldn't?
Asked in

Two members try to reserve the last available copy of a book at the same instant. What must the design guarantee, and where have you seen this exact problem before in this course?
Asked in

Think it through, then reveal:
A first-draft Library class handles adding books, checking books out, calculating overdue fines, AND sending email reminders. What's wrong, and how would you split it?
Asked in


The interviewer says: "Now support holds/reservations — a member can reserve a book that's currently checked out, and gets notified when it's returned." What pattern does this lean on, and why?
Asked in

FAQ
Should Library be a Singleton?
Only if the requirement is one central library system with a single point of coordination. A library chain with multiple branches would more naturally have one instance per branch, similar to the ParkingLot discussion in the previous lesson.
How is this different from a generic e-commerce or booking system?
Structurally very similar — Book/BookCopy mirrors Product/Inventory-item, and Loan mirrors an Order or Booking. Recognising this similarity is useful: many "different looking" LLD questions (library, equipment rental, event ticketing) share close to the same shape once you strip the domain-specific names away.
Do I need to model fines as actual payments here?
Not necessarily in depth — most interviewers are satisfied with `calculate()` returning the fine amount, with a one-line mention that an actual payment would be processed by a separate `PaymentService` (as seen conceptually in earlier lessons), rather than building that whole flow out.
Next, a system where State does almost all the work — the elevator can only ever be in one mode at a time — Lesson 12: Design an Elevator System →


