LLD CASE STUDIES / OBJECT DESIGN BRIEF

Design a library system

Design a library management system.

IntermediatePhase 09 / Topic 3 of 10ResponsibilitiesCollaborationsExtensibility
01

Overview

Design a library management system. Requirements: the catalog has books (title, author, ISBN) with multiple physical copies; members can search, borrow up to a limit, return, and reserve books; loans have due dates and late fines; librarians add books and copies; a member with unpaid fines above a threshold cannot borrow.

The central modeling insight is separating Book (catalog entry) from BookCopy (physical item with barcode and status). Loans link a member and a copy with dates. Reservations form a FIFO queue per book; when a copy is returned, the next reserver is notified (Observer) and the copy is held for them. Fine calculation is a strategy that may differ by member type.

A real library desk

The catalog card describes a book; the shelves hold several physical copies. The desk stamps a due date on your card, and if someone reserved the book, the returned copy goes to the hold shelf with their name on it.

02

When to use it

  • Classic LLD interview problem for entity modeling.
  • Practicing type-object separation and business rules.
  • Reservation queues and notifications.
03

Where it shows up in interviews

Catalog vs inventory

Recognize it when: one item type with many physical units.

  • Design a library system
  • Design a car rental system
  • Design an inventory system
Reservations and waitlists

Recognize it when: queue users for unavailable items.

  • Design a library system
  • Design a restaurant waitlist
04

Where it is used in real software

Integrated library systems

Koha and Evergreen model bibliographic records separately from item records with barcodes.

E-commerce inventory

Products (SKU) vs stock units in warehouses follow the same pattern.

Hold queues

Public libraries place holds in FIFO order and notify patrons when items are ready.

05

Key terms

Book
Catalog entry: ISBN, title, authors.
BookCopy
Physical item with barcode and status.
Loan
Member + copy + borrow date + due date + return date.
Reservation
Member waiting for a book (not a specific copy).
FinePolicy
Strategy computing late fees.
06

How it works, step by step

  1. 1
    Clarify

    Borrow limits, loan period, fines, reservations, search fields, roles.

  2. 2
    Entities

    Library, Book, BookCopy, Member, Loan, Reservation, FinePolicy, Notifier.

  3. 3
    Checkout rules

    Member active, under limit, no big fines, copy available or held for this member.

  4. 4
    Return flow

    Close loan, compute fine, hand copy to next reservation or mark available.

  5. 5
    Search

    Index by title, author, ISBN.

07

Checkout validation

Limit 3 loans, max fines 10.00

Step 1 / 5
Member situationResultRule
2 loans, no fines, copy availableAllowed-
3 loansRejectedBorrow limit reached
Fines 12.50RejectedUnpaid fines over threshold
Copy on hold for another memberRejectedReserved for next in queue
Copy on hold for this memberAllowedHold fulfilled

NOWMember situation: 2 loans, no fines, copy available | Result: Allowed | Rule: -

All rules live in one checkout method on the Library (facade), with data owned by the entities.

08

Implementation

import java.time.*;import java.util.*; record Book(String isbn, String title, String author) {} final class BookCopy {    enum Status { AVAILABLE, ON_LOAN, ON_HOLD, LOST }    final String barcode; final Book book;    Status status = Status.AVAILABLE;    String heldFor;                                   // member id when ON_HOLD    BookCopy(String barcode, Book book) { this.barcode = barcode; this.book = book; }} final class Member {    final String id; final String name;    final List<Loan> activeLoans = new ArrayList<>();    long finesCents;    Member(String id, String name) { this.id = id; this.name = name; }} final class Loan {    final BookCopy copy; final Member member; final LocalDate borrowed, due;    LocalDate returned;    Loan(BookCopy copy, Member member, LocalDate borrowed, LocalDate due) { this.copy = copy; this.member = member; this.borrowed = borrowed; this.due = due; }} interface FinePolicy { long fineCents(Loan loan, LocalDate returnedOn); }final class PerDayFine implements FinePolicy {    private final long perDay, cap;    PerDayFine(long perDay, long cap) { this.perDay = perDay; this.cap = cap; }    public long fineCents(Loan l, LocalDate on) { return Math.min(cap, Math.max(0, Duration.between(l.due.atStartOfDay(), on.atStartOfDay()).toDays()) * perDay); }} interface Notifier { void holdReady(Member m, Book b); } final class Library {    private static final int MAX_LOANS = 3, LOAN_DAYS = 14;    private static final long MAX_FINES = 1_000;     private final Map<String, Book> books = new HashMap<>();    private final Map<String, List<BookCopy>> copiesByIsbn = new HashMap<>();    private final Map<String, BookCopy> copiesByBarcode = new HashMap<>();    private final Map<String, Deque<Member>> reservations = new HashMap<>();    private final Map<String, Loan> loansByBarcode = new HashMap<>();    private final FinePolicy fines;    private final Notifier notifier;    private final Clock clock;     Library(FinePolicy fines, Notifier notifier, Clock clock) { this.fines = fines; this.notifier = notifier; this.clock = clock; }     void addCopy(Book book, String barcode) {        books.putIfAbsent(book.isbn(), book);        BookCopy copy = new BookCopy(barcode, book);        copiesByIsbn.computeIfAbsent(book.isbn(), k -> new ArrayList<>()).add(copy);        copiesByBarcode.put(barcode, copy);    }     List<Book> search(String text) {        String q = text.toLowerCase();        return books.values().stream().filter(b -> b.title().toLowerCase().contains(q) || b.author().toLowerCase().contains(q) || b.isbn().equals(text)).toList();    }     Loan checkout(Member m, String isbn) {        if (m.activeLoans.size() >= MAX_LOANS) throw new IllegalStateException("Borrow limit reached");        if (m.finesCents > MAX_FINES) throw new IllegalStateException("Unpaid fines over limit");        BookCopy copy = copiesByIsbn.getOrDefault(isbn, List.of()).stream()            .filter(c -> c.status == BookCopy.Status.AVAILABLE || (c.status == BookCopy.Status.ON_HOLD && m.id.equals(c.heldFor)))            .findFirst().orElseThrow(() -> new IllegalStateException("No copy available; reserve instead"));        LocalDate today = LocalDate.now(clock);        Loan loan = new Loan(copy, m, today, today.plusDays(LOAN_DAYS));        copy.status = BookCopy.Status.ON_LOAN;        copy.heldFor = null;        m.activeLoans.add(loan);        loansByBarcode.put(copy.barcode, loan);        return loan;    }     long returnCopy(String barcode) {        Loan loan = Optional.ofNullable(loansByBarcode.remove(barcode)).orElseThrow(() -> new IllegalArgumentException("Not on loan"));        LocalDate today = LocalDate.now(clock);        loan.returned = today;        loan.member.activeLoans.remove(loan);        long fine = fines.fineCents(loan, today);        loan.member.finesCents += fine;         BookCopy copy = loan.copy;        Deque<Member> queue = reservations.getOrDefault(copy.book.isbn(), new ArrayDeque<>());        Member next = queue.poll();        if (next != null) {            copy.status = BookCopy.Status.ON_HOLD;            copy.heldFor = next.id;            notifier.holdReady(next, copy.book);        } else {            copy.status = BookCopy.Status.AVAILABLE;        }        return fine;    }     void reserve(Member m, String isbn) {        if (!books.containsKey(isbn)) throw new IllegalArgumentException("Unknown ISBN");        Deque<Member> q = reservations.computeIfAbsent(isbn, k -> new ArrayDeque<>());        if (q.contains(m)) throw new IllegalStateException("Already reserved");        q.add(m);    }}
09

Complexity and performance

CheckoutO(copies of the book)

Small.

ReturnO(1) + queue poll

Hash lookups.

SearchO(books) naive

Use an inverted index at scale.

10

Trade-offs

Reserve a book vs a copy

Reserving the book (any copy) serves members faster than reserving a specific copy.

Fines on return vs daily job

Computing on return is simple; a daily job lets you block members with overdue items before they return them.

11

Variants and related techniques

Member types

Students vs faculty with different limits and fine policies (Strategy).

Digital lending

E-books with license counts instead of physical copies.

12

Common mistakes

  • Book and copy merged into one class.

    Fix: Separate catalog entries from physical items.

  • Returned copy made available despite a reservation queue.

    Fix: Check the queue and place the copy on hold.

  • Rules spread across UI and entities.

    Fix: Centralize in the Library service with entities owning their state.

13

Interview questions

Why separate Book and BookCopy?

A title can have many physical copies with different statuses and barcodes. Loans and holds apply to copies, while search and reservations apply to books.

How do reservations work when a copy is returned?

The return flow checks the book's FIFO reservation queue; if someone is waiting, the copy is placed on hold for them and they are notified, otherwise it becomes available.

14

Practice problems

ProblemDifficultyWhat it trains
Library with borrow limits and finesMediumRules.
Add reservations with hold expiryHardQueues and timers.