Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
🎯 Problem Statement
Design a library management system that can:- Manage books, members, and librarians
- Handle book borrowing and returns
- Track due dates and calculate fines
- Support book reservations
- Search books by title, author, or ISBN
📋 Step 1: Clarify Requirements
Questions to Ask the Interviewer
| Category | Question | Impact on Design |
|---|---|---|
| Books | Multiple copies of same book? | BookCopy vs Book |
| Members | Different member types? | Member hierarchy |
| Limits | Max books per member? Loan period? | Validation logic |
| Reservations | Can reserve borrowed books? | Reservation queue |
| Fines | How are fines calculated? | Fine calculator |
| Notifications | Due date reminders? | Observer pattern |
Functional Requirements
- Add/remove books from the library
- Register new members
- Borrow and return books
- Reserve books that are currently borrowed
- Search for books
- Calculate and collect fines for overdue books
- Send notifications for due dates
Non-Functional Requirements
- Support multiple copies of the same book
- Handle concurrent borrowing requests
- Maintain borrowing history
🧩 Step 2: Identify Core Objects
Book (metadata - title, author, ISBN) and BookCopy (physical copy - barcode, status). One book can have many copies!Books
Users
Transactions
Entity-Responsibility Mapping
| Entity | Responsibilities | Pattern |
|---|---|---|
Library | Manage catalog, coordinate operations | Singleton |
Book | Store metadata, track copies | - |
BookCopy | Track status, location | - |
Member | Borrow, return, pay fines | - |
Lending | Track borrow date, due date | - |
NotificationService | Due date reminders | Observer |
Key Constraints
📐 Step 3: Class Diagram
Step 4: Implementation
Enums and Constants
Book Classes
Member Class
Lending and Reservation
Library System
Step 5: Usage Example
Key Design Decisions
Why separate Book and BookCopy?
Why separate Book and BookCopy?
Why Strategy Pattern for Search?
Why Strategy Pattern for Search?
Why Queue-based Reservations?
Why Queue-based Reservations?
Interview Deep-Dive Questions
Q1: The search system uses the Strategy pattern with TitleSearchStrategy, AuthorSearchStrategy, etc. Why not just add search_by_title(), search_by_author() methods directly on Library? When does the Strategy pattern actually earn its keep here?
Q1: The search system uses the Strategy pattern with TitleSearchStrategy, AuthorSearchStrategy, etc. Why not just add search_by_title(), search_by_author() methods directly on Library? When does the Strategy pattern actually earn its keep here?
Q2: Two members both want the last available copy of a popular book. Walk me through exactly how the code prevents a race condition where both successfully borrow it.
Q2: Two members both want the last available copy of a popular book. Walk me through exactly how the code prevents a race condition where both successfully borrow it.
- The
Library.checkout_book()method is wrapped inwith self._lock, athreading.Lock(). This means only one thread can execute the checkout flow at a time. When Member A enters checkout, the lock is acquired. Member A’s call tobook.get_available_copy()finds the copy, andmember.borrow_book(copy)callscopy.checkout(member_id)which flips the status toBORROWED. When Member B’s thread gets the lock next,book.get_available_copy()returnsNonebecause the status is alreadyBORROWED, and checkout returnsNone. - The critical design choice is that the lock is at the
Librarylevel, not theBookCopylevel. This is a coarse-grained lock — simple and correct, but it means all checkout operations are serialized, even for completely unrelated books. If two members are borrowing different books simultaneously, one waits. For a library with dozens of concurrent checkouts, this becomes a bottleneck. - A finer-grained approach would lock per-ISBN or per-BookCopy. But then you risk deadlocks if a single operation needs to touch multiple books (e.g., “borrow these 3 books together”). The current approach trades throughput for simplicity and correctness — a valid choice for a system with modest concurrency.
- Example: Imagine a university library during the first week of semester — 200 students all trying to check out the same 10 textbooks. The coarse lock means each checkout takes ~1ms but they are fully serialized: 200 * 1ms = 200ms total. A per-book lock would allow parallel checkouts for different books but would be more complex to reason about.
- What if this system were a web app with multiple server processes (not threads)? The
threading.Lockwould be useless across processes. How would you handle concurrency — database-level locking, optimistic concurrency control, or something else? - The current code acquires the lock before checking if the member or book exists. If validation fails (member not found), the lock was held unnecessarily. How would you restructure the method to minimize lock contention while still preventing double-checkout?
Q3: A member returns a book 10 days late. Walk through the fine calculation, and then tell me every edge case or unfairness you can see in the current implementation.
Q3: A member returns a book 10 days late. Walk through the fine calculation, and then tell me every edge case or unfairness you can see in the current implementation.
- The fine calculation lives in
BookCopy.checkin(). It comparesdatetime.now()toself.due_date. If current time is past due date, it calculatesoverdue_days = (datetime.now() - self.due_date).daysand multiplies byFINE_PER_DAY(5.00. The fine is added tomember.total_fines, and if total fines exceed $10.00, the member cannot borrow more books until they pay. - Edge case 1 — Partial day rounding:
.daystruncates to whole days. A book returned 23 hours and 59 minutes late shows 0 days overdue and incurs no fine. A book returned 24 hours and 1 minute late shows 1 day. This is arguably fair (grace period of ~1 day) but is not intentional design — it is an accident oftimedelta.daysbehavior. - Edge case 2 — No fine cap: A member who loses a book and never returns it accumulates unbounded fines. There should be a maximum fine (e.g., the replacement cost of the book). Real libraries cap fines at the book’s value.
- Edge case 3 — No grace period: Some libraries offer a 1-3 day grace period before fines kick in. The code has no concept of this.
- Edge case 4 — Library closure days: If the library is closed for a holiday and the due date falls on that day, the member cannot return the book and is penalized for something outside their control. Real systems extend the due date to the next open day.
- Edge case 5 — Fine timing inconsistency: The fine is calculated at checkin time using
datetime.now(). If the member returns the book in the morning vs. the evening, the fine amount could differ. A date-only comparison (ignoring time) would be more fair. - Example: A member borrows a book, due date is Friday. Library is closed Saturday/Sunday. Member returns Monday morning. The code charges 3 days of fines ($1.50) for 2 days the library was closed.
- How would you implement a tiered fine system where the first 3 days are 0.50/day, and beyond 14 days the item is marked “lost” and the member is charged replacement cost? Where does this logic live — BookCopy, Member, or a new
FineCalculatorclass? - The member disputes a fine claiming they returned the book on time but the librarian scanned it late. How would you design an audit trail to resolve disputes — what events would you log and what timestamps matter?
Q4: A member reserves a book that is currently checked out. Walk through what happens from reservation to fulfillment, and identify the concurrency and fairness issues in the current design.
Q4: A member reserves a book that is currently checked out. Walk through what happens from reservation to fulfillment, and identify the concurrency and fairness issues in the current design.
- Reservation flow: Member calls
Library.reserve_book(). The code first checks if an available copy exists — if so, returnsNonebecause the member should just borrow it directly. Otherwise, it checks for duplicate pending reservations by the same member for the same book. If everything passes, aReservationobject is created with an expiry date (7 days from now) and appended to bothself.reservations(library-wide list) andmember.reservations. - Fulfillment flow: When someone returns a book,
Library.return_book()calls_notify_next_reservation(book). This iterates the reservation list and finds the first PENDING reservation for that book. If it is not expired, the member is notified. If expired, it is marked EXPIRED and the next reservation is checked. When the notified member comes to check out,_fulfill_reservation_if_existsmarks their reservation as FULFILLED. - Fairness issue 1 — No hold period after notification: The member is notified but there is no mechanism to hold a copy for them. Between notification and arrival, someone else could borrow the available copy. The reservation becomes meaningless.
- Fairness issue 2 — No copy reservation: The available copy is not linked to the reservation. If there are 2 copies and 3 reservations, returning one copy notifies the first reserver, but any member can borrow the copy before the reserver arrives.
- Fairness issue 3 — The list is not sorted:
self.reservationsis a flat list. The linear scan finds the “first” reservation, but insertion order depends on when members calledreserve_book, not on any priority system. A proper queue (or list sorted byreservation_date) would be clearer. - Example: Member A reserves “Clean Code” on Monday. Copy is returned Wednesday. Member A is notified via email. Member B walks in Wednesday afternoon, sees the copy on the shelf, and borrows it before Member A arrives Thursday. Member A’s reservation was effectively useless. A real system would mark the copy as “ON HOLD for 48 hours” and prevent anyone else from borrowing it.
- How would you implement a “hold shelf” mechanism where a returned copy is set aside for the first reserver for 48 hours before becoming generally available? What new status would you add to
BookStatusand where would the timeout logic run? - If the library system serves 10,000 members and a popular book has 200 reservations, the linear scan in
_notify_next_reservationbecomes expensive. How would you redesign the reservation storage for O(1) “next reserver” lookups?
Q5: The notification system currently just prints to console. How would you design a real notification system using the Observer pattern that supports email, SMS, and in-app notifications?
Q5: The notification system currently just prints to console. How would you design a real notification system using the Observer pattern that supports email, SMS, and in-app notifications?
- Define a
NotificationChannelabstract class with asend(member, message)method. ImplementEmailNotificationChannel,SMSNotificationChannel, andInAppNotificationChannel. Members have anotification_preferencesfield listing which channels they have opted into. When a notification event occurs (book available, due date approaching, fine accrued), the system iterates the member’s preferred channels and dispatches through each. - The Observer pattern fits naturally: notification-triggering events (book returned, reservation expiring, due date approaching) are the “subjects,” and the notification channels are the “observers.” But the key architectural decision is where the event is detected vs. where the notification is sent. Event detection lives in the domain logic (Library, Lending). Notification dispatch lives in a separate
NotificationServicethat the Library calls. This prevents domain classes from knowing about email APIs or SMS gateways. - For due-date reminders specifically, you need a background scheduled job (cron, Celery, or similar) that runs daily, scans all active lendings, and sends reminders for books due in 1-3 days. This is fundamentally different from event-driven notifications — it is poll-based. Both patterns coexist in a real system.
- Example: A member has opted into email and in-app but not SMS. When their reserved book becomes available,
NotificationServicesends an email via SendGrid and creates an in-app notification record in the database. If SendGrid is down, the system should retry with exponential backoff or fall back to SMS as a degraded mode — not silently fail.
send_email() calls in the Library class wherever something happens” — this scatters notification logic across the entire codebase, makes it impossible to test Library without an email server, and ensures that adding a new notification channel requires modifying every call site. It is the exact problem Observer solves.Follow-ups:- How do you handle notification failures (email bounces, SMS delivery fails) without blocking the core library operation? Should returning a book fail if the notification to the next reserver cannot be sent?
- Members complain about getting too many notifications. How would you design a notification batching or digest system — for example, “send me one daily summary instead of 5 individual emails”?
Q6: The Member class has can_borrow() which checks three conditions: active status, book limit, and outstanding fines. What is wrong with putting all this validation in the Member class, and how would you redesign it?
Q6: The Member class has can_borrow() which checks three conditions: active status, book limit, and outstanding fines. What is wrong with putting all this validation in the Member class, and how would you redesign it?
- The current design makes
Memberresponsible for knowing library business rules — the max book limit (5), the fine threshold ($10), and the status check. These are library policies, not member properties. If the library decides to change the max from 5 to 7, or offer premium members a limit of 10, you are editing theMemberclass — which should only model a member’s identity and state, not encode business rules. - A cleaner design extracts a
BorrowingPolicyclass (or set of policy objects) that the Library consults during checkout. The policy takes a member and returns a borrow-eligibility decision with a reason.StandardBorrowingPolicychecks the 5-book limit and 25 fine threshold.StudentBorrowingPolicyallows 3 books but no fine threshold (university pays). The Library selects the appropriate policy based on member type. - This also makes the system testable in isolation. You can test
StandardBorrowingPolicywithout creating a real Member object with borrowed books. You can testMemberwithout hardcoded constants. - Example: During exam season, the library temporarily raises the max books from 5 to 8 for all members. With the current design, you change a constant in Member (risky deploy). With a policy object, you swap in a
ExamSeasonBorrowingPolicy— no code change to Member at all.
- If different branches of the same library system have different borrowing limits (downtown allows 5, suburban allows 7), where does the branch-specific policy live? How does the Library class select the right policy at checkout time?
- The
total_fines > 10.0check uses a floating-point comparison. What can go wrong with accumulated floating-point fines (e.g., 20 returns of $0.50 each), and how would you fix this?
Q7: The current design uses Book (metadata) and BookCopy (physical instance). Why is this separation critical, and what breaks if you collapse them into a single class?
Q7: The current design uses Book (metadata) and BookCopy (physical instance). Why is this separation critical, and what breaks if you collapse them into a single class?
- The separation reflects a real-world truth: a library can own 15 copies of “Clean Code” and each copy has independent state — one is borrowed, one is on the hold shelf, one is lost, the rest are available. The
Bookclass stores shared, immutable metadata (ISBN, title, author) exactly once. EachBookCopystores instance-specific mutable state (barcode, status, due date, rack location). Without this split, you would duplicate metadata 15 times and risk inconsistency (what if one copy says author is “Robert Martin” and another says “Bob Martin”?). - From a data modeling perspective, this is a one-to-many relationship — one of the most fundamental patterns in system design. Collapsing it means you either cannot track individual copies (you just know “5 are available” as a count, but not which physical copy is where) or you duplicate metadata per copy. Tracking individual copies matters for: locating a specific copy on a shelf, auditing who had which copy, and handling damaged/lost copies without affecting the book’s overall catalog entry.
- The separation also enables a clean
get_available_copy()method onBookthat iterates copies and returns the first available one. If books and copies were merged, “find an available copy” becomes “find another row in the same table with the same ISBN and AVAILABLE status” — doable but loses the containment relationship. - Example: A member reports Book Copy #A3F2 of “Design Patterns” is missing pages. The librarian marks that specific copy as DAMAGED without affecting the other 4 copies. If Book and BookCopy were one class, marking damage would require a separate “damage” field per row and careful filtering everywhere.
count field for how many copies exist” — this is the classic mistake. A count tells you nothing about where each copy is, who has it, or its condition. You cannot track individual barcodes, due dates, or rack locations with a counter.Follow-ups:- If the library switches to eBooks where there is no physical copy but a license count (e.g., “3 concurrent readers allowed”), how would you model this? Does the Book/BookCopy split still apply, or do you need a different abstraction?
- A librarian wants to transfer 5 copies of a book from one branch to another. How does the current model support (or fail to support) multi-branch inventory management?
Q8: The system needs to handle a member who has 3 overdue books, $12 in fines, and tries to borrow a new book while also having an active reservation for a different book that just became available. Walk through every check and state transition that occurs.
Q8: The system needs to handle a member who has 3 overdue books, $12 in fines, and tries to borrow a new book while also having an active reservation for a different book that just became available. Walk through every check and state transition that occurs.
- Step 1 — Borrow attempt: The member calls
checkout_book. The Library acquires the lock, finds the member and book._fulfill_reservation_if_existschecks if the member has a pending reservation for this specific book — if so, marks it FULFILLED. Thenbook.get_available_copy()finds a copy.member.borrow_book(copy)callscan_borrow(). - Step 2 —
can_borrow()fails: Status is ACTIVE (pass).len(borrowed_books) = 3 < 5(pass). Buttotal_fines = 12.0 > 10.0(fail). The method returnsFalse.borrow_bookraises an exception: “Cannot borrow: limit reached or account suspended.” The checkout returnsNone. - Step 3 — Meanwhile, the reservation: The other book was returned by someone else.
_notify_next_reservationfound this member’s pending reservation and called_send_notification. But the member still cannot check out that reserved book becausecan_borrow()will fail again for the same fine-threshold reason. The reserved copy sits on the shelf, eventually the reservation expires after 7 days, and the next reserver is notified. - The fairness problem: The member is punished twice — they cannot borrow and their reservation effectively expires without them being able to act on it. A better design would either: (a) extend the reservation expiry while the member has outstanding fines, or (b) allow fine payment to immediately re-check pending reservations.
- Example: This exact scenario causes customer complaints in real libraries. The member pays their $12 fine but by then the 7-day reservation window has passed. The system should trigger a “re-evaluate reservations” check when fines are paid below the threshold.
- The
can_borrowcheck uses a hard threshold oftotal_fines > 10.0. Should reservation fulfillment bypass this check since the member already “earned” the book by reserving it? What are the arguments for and against? - If
pay_finereducestotal_finesbelow $10 and reactivates a suspended member, should the system automatically attempt to fulfill any pending reservations at that moment? How would you implement this without creating circular dependencies betweenpay_fineandcheckout_book?