CMD Guide
HomeOO & Low-Level DesignOO Design Problems

Design a Library Management System

A library management system works by separating the abstract thing a reader searches for (a Book — one ISBN, one title) from the physical thing they carry home (a BookItem — one barcode, one copy on one rack), so that loans, reservations, fines, and availability are all tracked per copy while search and catalog stay per title. Almost every requirement in this problem — "check out any copy", "reserve when none available", "who has this exact book" — falls out cleanly once you make that one split. Get the split wrong and the whole model fights you.

The core decomposition (and why)

The single most important modeling decision is the Book vs BookItem distinction. A reader searches the catalog for "Designing Data-Intensive Applications" — that is one Book (ISBN 978-1449373320). But the library owns three physical copies, each with its own barcode, rack, and loan state. Those are three BookItems. This is why each layer holds different responsibilities:

ConceptIdentityOwnsWhy it lives here
BookISBNtitle, authors, subject, publisherSearch and dedup happen per title, not per copy
BookItembarcodestatus, dueDate, rack, format, priceYou loan, reserve, and fine a specific copy
BookLending(barcode, member)creationDate, dueDate, returnDateAn event record — answers "who, when, due when"
BookReservation(barcode, member)status, queue positionDecouples "I want it" from "it's available"
Fine(member, lending)amount, paidMoney is auditable; never a field on the loan

An alternative some candidates reach for is folding lending and reservation state into boolean flags on BookItem (isLoaned, reservedBy). It compiles and demos fine — but it loses history (you can't answer "who borrowed this last March"), it can't model a reservation queue, and it can't represent "returned late, fine pending" without a third flag. Modeling lending and reservation as their own records keeps BookItem a thin state holder and pushes the workflow into objects you can query and audit.

diagram
diagram

Worked example: returning a book 3 days late

The fine path is where the original code was broken, so trace it with real values. Member M-42 borrowed copy LIB-0002; the library allows MAX_LENDING_DAYS = 10. Assume a flat fine of $0.50 per day late. The dates:

StepValue
Borrowed on2026-06-10
Due date (borrowed + 10)2026-06-20
Actually returned (today)2026-06-23
today.compareTo(dueDate)> 0 → overdue, enter fine branch
diff = today.getTime() - dueDate.getTime()259,200,000 ms
diffDays = diff / (24*60*60*1000)3 days
Fine.collectFine(memberId, 3)3 × $0.50 = $1.50

That arithmetic only runs if the code names the right variable. The original wrote today.compareTo(...) on one line and then todayDate.getTime() on the next — two different names for the same instant. It does not compile, so the fine is never computed.

diagram
diagram

Why the naive version is wrong

The shipped code had three defects that all prevent compilation, plus a behavioral bug. Each is small, and each is the kind of thing an interviewer will catch:

  1. Undeclared variable. checkForFine declares Date today = new Date(); then uses todayDate.getTime(). todayDate was never declared — compile error. (It also reads memberId, which isn't in scope; the fix threads it through from the lending record.)
  2. Phantom enum constant. Both checkoutBookItem and renewBookItem call bookReservation.updateStatus(ReservationStatus.COMPLETED), but ReservationStatus only defines WAITING, PENDING, CANCELED, NONE. COMPLETED doesn't exist — compile error, and a real modeling gap: there was no way to mark a reservation fulfilled. The fix adds COMPLETED.
  3. Non-existent type. public bool renewBookItem(...) — Java has no bool type; the keyword is boolean. Compile error.

Here is the corrected Member class. The enum gains COMPLETED, today is used consistently, memberId is read from the lending record, and renewBookItem returns boolean:

public enum ReservationStatus {
    WAITING, PENDING, CANCELED, COMPLETED, NONE   // added COMPLETED
}

public class Member extends Account {
    private Date dateOfMembership;
    private int totalBooksCheckedout;

    public int getTotalBooksCheckedout() { return totalBooksCheckedout; }
    private void incrementTotalBooksCheckedout() { totalBooksCheckedout++; }
    private void decrementTotalBooksCheckedout() { totalBooksCheckedout--; }

    public boolean checkoutBookItem(BookItem bookItem) {
        if (getTotalBooksCheckedout() >= Constants.MAX_BOOKS_ISSUED_TO_A_USER) {
            ShowError("User already has the maximum number of books checked out");
            return false;
        }
        BookReservation reservation =
            BookReservation.fetchReservationDetails(bookItem.getBarcode());
        // reserved by someone else -> refuse
        if (reservation != null && !reservation.getMemberId().equals(getId())) {
            ShowError("This book is reserved by another member");
            return false;
        } else if (reservation != null) {
            // this member's own reservation is now fulfilled
            reservation.updateStatus(ReservationStatus.COMPLETED);
        }
        if (!bookItem.checkout(getId())) return false;
        incrementTotalBooksCheckedout();
        return true;
    }

    private void checkForFine(String bookItemBarcode) {
        BookLending lending = BookLending.fetchLendingDetails(bookItemBarcode);
        Date dueDate = lending.getDueDate();
        Date today   = new Date();
        if (today.compareTo(dueDate) > 0) {                 // overdue
            long diff     = today.getTime() - dueDate.getTime();  // was todayDate
            long diffDays = diff / (24L * 60 * 60 * 1000);
            Fine.collectFine(lending.getMemberId(), diffDays);    // memberId from record
        }
    }

    public void returnBookItem(BookItem bookItem) {
        checkForFine(bookItem.getBarcode());
        BookReservation reservation =
            BookReservation.fetchReservationDetails(bookItem.getBarcode());
        if (reservation != null) {
            // someone is waiting -> hold it for them, notify
            bookItem.updateBookItemStatus(BookStatus.RESERVED);
            reservation.sendBookAvailableNotification();
        } else {
            bookItem.updateBookItemStatus(BookStatus.AVAILABLE);
        }
        decrementTotalBooksCheckedout();
    }

    public boolean renewBookItem(BookItem bookItem) {          // was 'bool'
        checkForFine(bookItem.getBarcode());
        BookReservation reservation =
            BookReservation.fetchReservationDetails(bookItem.getBarcode());
        // reserved by someone else -> cannot renew, hand it over
        if (reservation != null && !reservation.getMemberId().equals(getId())) {
            ShowError("This book is reserved by another member");
            bookItem.updateBookItemStatus(BookStatus.RESERVED);
            reservation.sendBookAvailableNotification();
            return false;
        } else if (reservation != null) {
            reservation.updateStatus(ReservationStatus.COMPLETED);
        }
        BookLending.lendBook(bookItem.getBarcode(), getId());
        bookItem.updateDueDate(LocalDate.now().plusDays(Constants.MAX_LENDING_DAYS));
        return true;
    }
}

Note one behavioral fix beyond the compile errors: the original returnBookItem always ended by setting the item to AVAILABLE even after setting it to RESERVED for the next member, silently dropping the hold. The corrected version uses an else so a reserved copy stays reserved. It also decrements the member's count on return, which the original omitted — without it, a member who returns five books still shows five checked out and can never borrow again.

Pitfalls

Takeaways


Re-authored and deepened for this guide. The Book/BookItem decomposition, use cases, and class roles follow the classic treatment in Grokking the Object Oriented Design Interview (Design Gurus / educative.io). The three compile defects (todayDate vs today, the missing ReservationStatus.COMPLETED, and bool vs boolean), the set-then-overwrite status bug, and the missing decrement-on-return were identified and fixed here; the fine worked example uses concrete dates. Java type and date-handling guidance per the Java Language Specification and java.time (JSR-310) documentation.

🔨 Practice this hands-on — Design a Library Management System →
Attempt it from an empty file, break it to feel the failure, then defend it under pushback.
🤖 Don't fully get this? Learn it with Claude

Stuck on Design a Library Management System? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Design a Library Management System** (OO & Low-Level Design) and want to truly understand it. Explain Design a Library Management System from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Design a Library Management System** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Design a Library Management System** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Design a Library Management System** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes