System Design

Library Management

20 min

Library Management

Library Management System - Low-Level Design

Overview

Design a library management system that handles book inventory, member management, book borrowing/returning, fines calculation, and reservation system.

Requirements

Functional Requirements

  • Add/remove/update books
  • Register members
  • Issue books to members
  • Return books
  • Calculate fines for late returns
  • Reserve books
  • Search books by title, author, ISBN
  • Track book availability
  • Generate reports

Non-Functional Requirements

  • Support 1000+ concurrent users
  • Fast search (<100ms)
  • Reliable fine calculation
  • Data persistence
  • Audit logging

Class Diagram

Main Class
+ properties
+ attributes
+ methods()
+ operations()
Helper Class
+ fields
+ data
+ helpers()
+ utilities()
Interface
«interface»
+ abstract methods()
━━━▶ Dependency
◆━━━ Composition
△━━━ Inheritance

Implementation

Book Class

java
1public enum BookStatus { 2 AVAILABLE, ISSUED, RESERVED, LOST 3} 4 5public class Book { 6 private String ISBN; 7 private String title; 8 private String author; 9 private String publisher; 10 private int publicationYear; 11 private String category; 12 private BookStatus status; 13 private String rackNumber; 14 15 public Book(String ISBN, String title, String author) { 16 this.ISBN = ISBN; 17 this.title = title; 18 this.author = author; 19 this.status = BookStatus.AVAILABLE; 20 } 21 22 public boolean isAvailable() { 23 return status == BookStatus.AVAILABLE; 24 } 25 26 public void markIssued() { 27 if (!isAvailable()) { 28 throw new IllegalStateException("Book is not available"); 29 } 30 this.status = BookStatus.ISSUED; 31 } 32 33 public void markReturned() { 34 this.status = BookStatus.AVAILABLE; 35 } 36 37 public void markLost() { 38 this.status = BookStatus.LOST; 39 } 40 41 // Getters and setters 42 public String getISBN() { return ISBN; } 43 public String getTitle() { return title; } 44 public String getAuthor() { return author; } 45 public BookStatus getStatus() { return status; } 46}

Member Class

java
1public enum MembershipType { 2 BASIC(2, 14), // max 2 books, 14 days 3 PREMIUM(5, 30), // max 5 books, 30 days 4 GOLD(10, 60); // max 10 books, 60 days 5 6 private final int maxBooks; 7 private final int borrowDays; 8 9 MembershipType(int maxBooks, int borrowDays) { 10 this.maxBooks = maxBooks; 11 this.borrowDays = borrowDays; 12 } 13 14 public int getMaxBooks() { return maxBooks; } 15 public int getBorrowDays() { return borrowDays; } 16} 17 18public class Member { 19 private String memberId; 20 private String name; 21 private String email; 22 private String phone; 23 private LocalDate registrationDate; 24 private MembershipType membershipType; 25 private Set<String> issuedBookISBNs; 26 private double outstandingFine; 27 28 public Member(String memberId, String name, String email, 29 MembershipType type) { 30 this.memberId = memberId; 31 this.name = name; 32 this.email = email; 33 this.membershipType = type; 34 this.registrationDate = LocalDate.now(); 35 this.issuedBookISBNs = new HashSet<>(); 36 this.outstandingFine = 0.0; 37 } 38 39 public boolean canBorrowMoreBooks() { 40 return issuedBookISBNs.size() < membershipType.getMaxBooks() 41 && outstandingFine == 0; 42 } 43 44 public void borrowBook(String ISBN) { 45 if (!canBorrowMoreBooks()) { 46 throw new IllegalStateException("Cannot borrow more books"); 47 } 48 issuedBookISBNs.add(ISBN); 49 } 50 51 public void returnBook(String ISBN) { 52 issuedBookISBNs.remove(ISBN); 53 } 54 55 public void addFine(double amount) { 56 this.outstandingFine += amount; 57 } 58 59 public void payFine(double amount) { 60 this.outstandingFine = Math.max(0, outstandingFine - amount); 61 } 62 63 // Getters 64 public String getMemberId() { return memberId; } 65 public int getIssuedBooksCount() { return issuedBookISBNs.size(); } 66 public double getOutstandingFine() { return outstandingFine; } 67 public MembershipType getMembershipType() { return membershipType; } 68}

BookIssue Class

java
1public class BookIssue { 2 private String issueId; 3 private String memberId; 4 private String ISBN; 5 private LocalDate issueDate; 6 private LocalDate dueDate; 7 private LocalDate returnDate; 8 private double fine; 9 10 public BookIssue(String memberId, String ISBN, int borrowDays) { 11 this.issueId = UUID.randomUUID().toString(); 12 this.memberId = memberId; 13 this.ISBN = ISBN; 14 this.issueDate = LocalDate.now(); 15 this.dueDate = issueDate.plusDays(borrowDays); 16 this.fine = 0.0; 17 } 18 19 public double calculateFine(double finePerDay) { 20 if (returnDate == null) { 21 returnDate = LocalDate.now(); 22 } 23 24 if (returnDate.isAfter(dueDate)) { 25 long daysLate = ChronoUnit.DAYS.between(dueDate, returnDate); 26 fine = daysLate * finePerDay; 27 } 28 29 return fine; 30 } 31 32 public void markReturned() { 33 this.returnDate = LocalDate.now(); 34 } 35 36 public boolean isOverdue() { 37 return LocalDate.now().isAfter(dueDate) && returnDate == null; 38 } 39 40 // Getters 41 public String getIssueId() { return issueId; } 42 public String getMemberId() { return memberId; } 43 public String getISBN() { return ISBN; } 44 public LocalDate getDueDate() { return dueDate; } 45 public double getFine() { return fine; } 46}

BookReservation Class

java
1public class BookReservation { 2 private String reservationId; 3 private String memberId; 4 private String ISBN; 5 private LocalDate reservationDate; 6 private LocalDate expiryDate; 7 private boolean isActive; 8 9 public BookReservation(String memberId, String ISBN) { 10 this.reservationId = UUID.randomUUID().toString(); 11 this.memberId = memberId; 12 this.ISBN = ISBN; 13 this.reservationDate = LocalDate.now(); 14 this.expiryDate = reservationDate.plusDays(3); // 3-day hold 15 this.isActive = true; 16 } 17 18 public boolean isExpired() { 19 return LocalDate.now().isAfter(expiryDate); 20 } 21 22 public void cancel() { 23 this.isActive = false; 24 } 25 26 // Getters 27 public String getReservationId() { return reservationId; } 28 public String getMemberId() { return memberId; } 29 public String getISBN() { return ISBN; } 30 public boolean isActive() { return isActive && !isExpired(); } 31}

Library Class (Main Service)

java
1public class Library { 2 private static final double FINE_PER_DAY = 1.0; 3 4 private Map<String, Book> catalog; // ISBN -> Book 5 private Map<String, Member> members; // memberId -> Member 6 private Map<String, BookIssue> activeIssues; // issueId -> BookIssue 7 private Map<String, List<BookReservation>> reservations; // ISBN -> List 8 private SearchIndex searchIndex; 9 10 public Library() { 11 this.catalog = new HashMap<>(); 12 this.members = new HashMap<>(); 13 this.activeIssues = new HashMap<>(); 14 this.reservations = new HashMap<>(); 15 this.searchIndex = new SearchIndex(); 16 } 17 18 // Book Management 19 public void addBook(Book book) { 20 if (catalog.containsKey(book.getISBN())) { 21 throw new IllegalArgumentException("Book already exists"); 22 } 23 catalog.put(book.getISBN(), book); 24 searchIndex.indexBook(book); 25 } 26 27 public void removeBook(String ISBN) { 28 Book book = catalog.get(ISBN); 29 if (book != null && book.getStatus() != BookStatus.ISSUED) { 30 catalog.remove(ISBN); 31 searchIndex.removeBook(ISBN); 32 } else { 33 throw new IllegalStateException("Cannot remove issued book"); 34 } 35 } 36 37 // Member Management 38 public void registerMember(Member member) { 39 if (members.containsKey(member.getMemberId())) { 40 throw new IllegalArgumentException("Member already exists"); 41 } 42 members.put(member.getMemberId(), member); 43 } 44 45 public void removeMember(String memberId) { 46 Member member = members.get(memberId); 47 if (member != null && member.getIssuedBooksCount() == 0 48 && member.getOutstandingFine() == 0) { 49 members.remove(memberId); 50 } else { 51 throw new IllegalStateException( 52 "Cannot remove member with issued books or pending fines"); 53 } 54 } 55 56 // Issue Book 57 public BookIssue issueBook(String memberId, String ISBN) { 58 Member member = members.get(memberId); 59 Book book = catalog.get(ISBN); 60 61 if (member == null) { 62 throw new IllegalArgumentException("Member not found"); 63 } 64 if (book == null) { 65 throw new IllegalArgumentException("Book not found"); 66 } 67 if (!member.canBorrowMoreBooks()) { 68 throw new IllegalStateException( 69 "Member cannot borrow more books"); 70 } 71 72 // Check if book is reserved for someone else 73 if (hasActiveReservation(ISBN) 74 && !isReservedForMember(ISBN, memberId)) { 75 throw new IllegalStateException( 76 "Book is reserved for another member"); 77 } 78 79 if (!book.isAvailable()) { 80 throw new IllegalStateException("Book is not available"); 81 } 82 83 // Issue the book 84 book.markIssued(); 85 member.borrowBook(ISBN); 86 87 int borrowDays = member.getMembershipType().getBorrowDays(); 88 BookIssue issue = new BookIssue(memberId, ISBN, borrowDays); 89 activeIssues.put(issue.getIssueId(), issue); 90 91 // Cancel reservation if exists 92 cancelReservation(ISBN, memberId); 93 94 return issue; 95 } 96 97 // Return Book 98 public double returnBook(String issueId) { 99 BookIssue issue = activeIssues.get(issueId); 100 if (issue == null) { 101 throw new IllegalArgumentException("Issue not found"); 102 } 103 104 Member member = members.get(issue.getMemberId()); 105 Book book = catalog.get(issue.getISBN()); 106 107 // Mark return and calculate fine 108 issue.markReturned(); 109 double fine = issue.calculateFine(FINE_PER_DAY); 110 111 if (fine > 0) { 112 member.addFine(fine); 113 } 114 115 // Update states 116 member.returnBook(issue.getISBN()); 117 book.markReturned(); 118 activeIssues.remove(issueId); 119 120 return fine; 121 } 122 123 // Reserve Book 124 public BookReservation reserveBook(String memberId, String ISBN) { 125 Member member = members.get(memberId); 126 Book book = catalog.get(ISBN); 127 128 if (member == null || book == null) { 129 throw new IllegalArgumentException("Invalid member or book"); 130 } 131 132 if (book.isAvailable()) { 133 throw new IllegalStateException( 134 "Book is available, no need to reserve"); 135 } 136 137 BookReservation reservation = new BookReservation(memberId, ISBN); 138 reservations.computeIfAbsent(ISBN, k -> new ArrayList<>()) 139 .add(reservation); 140 141 return reservation; 142 } 143 144 // Search Books 145 public List<Book> searchBooks(String query) { 146 List<String> ISBNs = searchIndex.search(query); 147 return ISBNs.stream() 148 .map(catalog::get) 149 .filter(Objects::nonNull) 150 .collect(Collectors.toList()); 151 } 152 153 // Helper methods 154 private boolean hasActiveReservation(String ISBN) { 155 List<BookReservation> bookReservations = reservations.get(ISBN); 156 return bookReservations != null && 157 bookReservations.stream().anyMatch(BookReservation::isActive); 158 } 159 160 private boolean isReservedForMember(String ISBN, String memberId) { 161 List<BookReservation> bookReservations = reservations.get(ISBN); 162 if (bookReservations == null) return false; 163 164 return bookReservations.stream() 165 .filter(BookReservation::isActive) 166 .findFirst() 167 .map(r -> r.getMemberId().equals(memberId)) 168 .orElse(false); 169 } 170 171 private void cancelReservation(String ISBN, String memberId) { 172 List<BookReservation> bookReservations = reservations.get(ISBN); 173 if (bookReservations != null) { 174 bookReservations.stream() 175 .filter(r -> r.getMemberId().equals(memberId) && r.isActive()) 176 .forEach(BookReservation::cancel); 177 } 178 } 179 180 // Get overdue books 181 public List<BookIssue> getOverdueIssues() { 182 return activeIssues.values().stream() 183 .filter(BookIssue::isOverdue) 184 .collect(Collectors.toList()); 185 } 186}

Search Index

java
1public class SearchIndex { 2 private Map<String, Set<String>> titleIndex; 3 private Map<String, Set<String>> authorIndex; 4 private Map<String, String> isbnIndex; 5 6 public SearchIndex() { 7 this.titleIndex = new HashMap<>(); 8 this.authorIndex = new HashMap<>(); 9 this.isbnIndex = new HashMap<>(); 10 } 11 12 public void indexBook(Book book) { 13 // Index by title words 14 String[] titleWords = book.getTitle().toLowerCase().split("\\s+"); 15 for (String word : titleWords) { 16 titleIndex.computeIfAbsent(word, k -> new HashSet<>()) 17 .add(book.getISBN()); 18 } 19 20 // Index by author 21 String author = book.getAuthor().toLowerCase(); 22 authorIndex.computeIfAbsent(author, k -> new HashSet<>()) 23 .add(book.getISBN()); 24 25 // Index by ISBN 26 isbnIndex.put(book.getISBN(), book.getISBN()); 27 } 28 29 public void removeBook(String ISBN) { 30 // Remove from all indices 31 titleIndex.values().forEach(set -> set.remove(ISBN)); 32 authorIndex.values().forEach(set -> set.remove(ISBN)); 33 isbnIndex.remove(ISBN); 34 } 35 36 public List<String> search(String query) { 37 Set<String> results = new HashSet<>(); 38 String lowerQuery = query.toLowerCase(); 39 40 // Search in ISBN 41 if (isbnIndex.containsKey(query)) { 42 results.add(query); 43 } 44 45 // Search in title 46 titleIndex.entrySet().stream() 47 .filter(e -> e.getKey().contains(lowerQuery)) 48 .flatMap(e -> e.getValue().stream()) 49 .forEach(results::add); 50 51 // Search in author 52 authorIndex.entrySet().stream() 53 .filter(e -> e.getKey().contains(lowerQuery)) 54 .flatMap(e -> e.getValue().stream()) 55 .forEach(results::add); 56 57 return new ArrayList<>(results); 58 } 59}

Usage Example

java
1public class LibraryDemo { 2 public static void main(String[] args) { 3 Library library = new Library(); 4 5 // Add books 6 Book book1 = new Book("978-0134685991", 7 "Effective Java", 8 "Joshua Bloch"); 9 library.addBook(book1); 10 11 // Register member 12 Member member = new Member("M001", 13 "John Doe", 14 "john@example.com", 15 MembershipType.BASIC); 16 library.registerMember(member); 17 18 // Issue book 19 try { 20 BookIssue issue = library.issueBook("M001", "978-0134685991"); 21 System.out.println("Book issued. Due date: " + issue.getDueDate()); 22 23 // Simulate late return 24 Thread.sleep(1000); 25 double fine = library.returnBook(issue.getIssueId()); 26 System.out.println("Fine: $" + fine); 27 28 } catch (Exception e) { 29 System.out.println("Error: " + e.getMessage()); 30 } 31 32 // Search books 33 List<Book> results = library.searchBooks("Java"); 34 System.out.println("Found " + results.size() + " books"); 35 } 36}

Design Patterns Used

  1. Factory Pattern: Creating different membership types
  2. Strategy Pattern: Different fine calculation strategies
  3. Observer Pattern: Notify members about due dates
  4. State Pattern: Book status transitions

Trade-offs

AspectChoiceTrade-off
SearchIn-memory indexSpeed vs memory
StorageHashMapFast access vs ordered data
Fine calculationOn returnSimplicity vs real-time
ReservationQueueFair vs complex

💡 Interview Tips & Out-of-the-Box Thinking

Common Pitfalls

  • Not handling multiple copies: One ISBN can have multiple physical copies - need copy ID
  • No reservation queue: Popular books should have waitlist system
  • Hardcoded fine calculation: Should be configurable strategy (daily rate, grace period)
  • Missing transaction history: Need audit trail for disputes

Design Pattern Recognition

  • Singleton: Single Library instance per branch
  • Strategy: Different fine calculation strategies (fixed, progressive, waiver for students)
  • Observer: Notify members when reserved book becomes available
  • State: Book states (Available → Issued → Overdue → Returned)
  • Factory: Create different member types (Student, Faculty, Guest)

Advanced Considerations

  • Concurrent borrowing: Two members try to issue last copy - need distributed locks
  • Batch operations: Process returns/fines overnight instead of real-time
  • Search optimization: Elasticsearch for full-text search across titles/authors/descriptions
  • Multi-branch support: Transfer books between branches, unified catalog
  • Historical data: Archive old transactions for analytics without bloating active DB

Creative Solutions

  • Smart recommendations: ML-based "users who borrowed this also borrowed..."
  • Dynamic due dates: Extend for books with no reservations, stricter for popular books
  • Fine amnesty periods: Waive fines during exam periods to encourage returns
  • E-book integration: Seamlessly handle both physical and digital books
  • RFID tagging: Self-checkout kiosks with RFID scanners

Trade-off Discussions

  • In-memory vs Database: Fast but volatile vs Persistent but slower
  • Real-time fines vs Batch: Immediate feedback vs Lower compute overhead
  • Strict limits vs Flexible: Max 3 books (fair) vs Dynamic based on return history (optimized)
  • Reservation expiry: Hold book for 24 hours (turn around) vs 7 days (member convenience)

Edge Cases

  • Book lost before return: How to handle fine + replacement cost? (Answer: Mark as LOST, charge replacement + processing fee)
  • Member with overdue fines: Can they borrow more? (Answer: Block until fines paid)
  • Book damaged on return: Who assesses damage? (Answer: Librarian inspection + damage codes)
  • Reservation conflicts: Two members reserved at same time (Answer: Timestamp-based priority queue)
  • Renewal limits: How many times can member renew? (Answer: Max 2 renewals if no reservations)
  • Bulk return: Member returns 20 books at once (Answer: Batch process to avoid blocking)

Follow-up Questions

  1. How to handle multiple copies of the same book?
  2. How to implement a waitlist for popular books?
  3. How to send automated reminders for due dates?
  4. How to implement damage/loss handling?
  5. How to generate monthly reports?

Class Diagram

Library Management Class Diagram

Detailed class/schema diagram will be displayed here

Press j for next, k for previous