Library Management
Design the desk software for a library: members check books out, return them, put a hold on a copy that's out, and get charged if they're late. It's a small domain, which is exactly why it's a good interview prompt - there's nowhere to hide a sloppy model behind sheer size, and the fee math has a habit of leaking into three classes if you let it.
Requirements
Functional
- A
Catalogmaps aBook(title, author, ISBN) to one or more physicalCopys. - A
Memberchecks out an available copy, creating aLoan; returning it closes the loan and frees the copy. - If every copy of a book is checked out, a member can place a
Hold; the next return of that book should go to the earliest hold in line, not back onto the open shelf. - Overdue loans accrue a fee, calculated per day late.
Non-functional
- The overdue fee formula (flat per-day today, tiered or capped tomorrow) must not require
editing
LoanorMember. - Checking whether a book is available, and who is next in line for it, must not require scanning every loan or hold in the system.
Design
Copy is the unit that actually moves between "on the shelf" and "checked out" - Book is
just catalog metadata shared by every copy of it. Fee math is pulled into a
FeePolicy the same way FeeStrategy handles pricing in
Parking Lot: Loan asks it for a number and never
computes one itself.
- 1The member never touches a Copy directly - the catalog is the only class that finds one.
- 2The book (not the catalog) knows which of its own copies are on the shelf.
- 3A loan is created for the specific copy found - not for "a copy of this book" in the abstract.
- 4Returning goes through the loan itself, since it is the one record of who has this exact copy.
- 5The book decides whether the returned copy satisfies its own hold queue or goes back on the shelf.
Each Book keeps its own hold queue, so "who's next" is a peek at that book's queue, and
returning a copy either satisfies the head of that queue or puts the copy back on the
shelf - never both.
ON_HOLD is drawn as terminal on purpose, not as an oversight: nowhere in this code does a
copy ever leave ON_HOLD - findAvailableCopy() filters for AVAILABLE only, so an
on-hold copy cannot even be checked out by the member it was held for. That gap is exactly
what the "what's missing" section below calls out in prose; the diagram just makes it
visible without needing the prose to be read first.
Class diagram
Code
Design decisions
BookandCopyare separate classes, not one class with acopiesAvailablecounter. A counter can tell you how many copies are free, but not which one - and a hold needs to be satisfied by a specific physical copy being returned, not by a number ticking up. Splitting them means aLoanand aHoldcan each point at the exact copy they care about.- Overdue fee calculation is a
FeePolicy, resolved once perLoan.returnBook()call. Same reasoning as the parking lot'sFeeStrategy: pricing is the part of a library that changes when policy changes (a fee cap, a grace period, different rates for reference vs. lending copies), so isolating it meansLoannever edits when the fee schedule does. - The hold queue lives on
Book, not on a globalHoldManager. A hold is always "next in line for this book," so keeping the queue where the book already lives makes "who gets this copy next" a local check on return, instead of a query that has to filter a system-wide hold table by book first. - What's missing for a real system: hold expiration (a member who doesn't pick up
within 48 hours loses their place) needs a scheduled sweep this design doesn't include,
and multi-branch libraries need
Copyto carry abranchand holds to express a pickup-location preference - both left out to keep checkout/return/hold the whole focus.
Common follow-ups
- How would you let a member choose a pickup branch for a hold? Add a
branchfield toHoldand changeBook.resolveReturnto only satisfy a hold whose branch matches the returning copy's branch, skipping (not popping) a hold for a different branch - a change local toBookandHold, not toCatalogorLoan. - What happens if a member never picks up a satisfied hold? Needs a scheduled sweep that
checks how long a copy has sat
ON_HOLDand, past a cutoff, callsBook.resolveReturnagain to offer it to the next hold or the shelf - a new process, not a change to checkout/return themselves. - How would you cap unpaid overdue fees before blocking further checkouts?
Catalog.checkoutwould need to check the member's accumulated unpaid fees (a new field or lookup onMember) before callingfindAvailableCopy-LoanandFeePolicystay unchanged since they only ever calculate a fee, never enforce account-wide limits. - How do you support reference-only copies that can never be checked out? Add a
checkoutableflag toCopyand haveBook.findAvailableCopyskip copies where it's false -LoanandFeePolicyare unaffected since they only ever operate on a copy already handed to them.
Check yourself
Why are Book and Copy separate classes instead of Book holding a copiesAvailable counter?