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
- Factory Pattern: Creating different membership types
- Strategy Pattern: Different fine calculation strategies
- Observer Pattern: Notify members about due dates
- State Pattern: Book status transitions
Trade-offs
| Aspect | Choice | Trade-off |
|---|---|---|
| Search | In-memory index | Speed vs memory |
| Storage | HashMap | Fast access vs ordered data |
| Fine calculation | On return | Simplicity vs real-time |
| Reservation | Queue | Fair 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
- How to handle multiple copies of the same book?
- How to implement a waitlist for popular books?
- How to send automated reminders for due dates?
- How to implement damage/loss handling?
- How to generate monthly reports?
Class Diagram
Library Management Class Diagram
Detailed class/schema diagram will be displayed here