Library System
By the Syncfusion DevTutorials team · Last updated
Design a library: copies, loans with due days and fines, a three-book limit and a fair waiting list, built against hidden tests.
QuickBite has a small office library with cookery and technology books. The library currently uses a notebook to record who borrowed each book, when it is due, and who is waiting for it.
Managing these details by hand can lead to missing records, incorrect due dates, and confusion about whose turn is next.
In this case study, you will design a simple library management system for borrowing and returning books, tracking due dates, calculating fines, and maintaining a fair waiting list.
In simple words: you will replace the notebook with three small classes. Each class will have a clear responsibility, and automated tests will check whether the library rules work correctly.
What You Will Learn
In this case study, you will:
- Tell a book title apart from its copies.
- Record each loan with its due day and fine.
- Check borrowing rules in a clear order.
- Keep a fair, first-come waiting list.
- Build the library in the sandbox and pass the hidden tests.
Library, hotel and movie-rental questions are all the same shape: things, people, and a record of who has what until when. Spot that shape and the classes follow.
Step 1: Agree on the Requirements
Before designing classes or writing code, clearly define what the system must do.
In a system design interview, explain these requirements to the interviewer and check that the scope is correct. This helps avoid building features that are not required.
What the System Must Do
The library system must support the following operations:
addBook(isbn, title, copies)adds a book and its available copies to the library.borrow(member, isbn, day)lends an available copy to a member. The copy is due 14 days after it is borrowed.- A member can borrow no more than three books at a time.
- A member cannot borrow two copies of the same book.
giveBack(member, isbn, day)records the return and calculates a fine of 10 for each day the book is late.reserve(member, isbn)adds a member to the waiting list when no copy is available.- When a copy is returned, it is reserved for the member who has waited the longest.
Here, isbn is the unique number used to identify a book title, while day is the day number used to calculate due dates and fines.
What the System Will Not Include
To keep this case study focused, the first version will not include:
- Searching the library catalogue
- Renewing a loan
- Paying fines online
These features can be added later. Leaving them out now keeps the design small enough to understand, build, and test.
Step 2: Find the Classes
A simple trick: underline the nouns in the requirements. Most of them become classes. The verbs become methods.
| Class | Its one job |
|---|---|
| Book | Knows the title, the number of free copies and the waiting list |
| Loan | Remembers the member, the book and the due day |
| Library | Checks the rules, lends and takes back books |
The class diagram for this design.
Step 3: Build the Key Parts
A Book Is Not a Copy
The library has one title, "Clean Code", but three physical copies. The title is the same for everyone; the number of free copies changes.
The picture shows one title with three copies, after one copy is lent out:
One Book, three copies. After one loan, Clean Code has 2 of 3 copies free.
Here is the same idea in code:
A book title is not the same as a copy
class Book {
constructor(readonly isbn: string, readonly title: string, public free: number) {}
}
const cleanCode = new Book("111", "Clean Code", 3);
cleanCode.free--; // one copy lent out
console.log(`${cleanCode.title}: ${cleanCode.free} of 3 copies free`);
Expected output:
Clean Code: 2 of 3 copies free
The Loan
A loan is the record of one member having one copy. It remembers the due day, so it can work out the fine.
Step through asha's loan. She borrows on day 0, so it is due on day 14. The fine is 10 a day:
Here is the same idea in code:
A loan remembers who, what and when it is due
class Loan {
constructor(readonly member: string, readonly isbn: string, readonly due: number) {}
fine(returnDay: number, perDay: number): number {
return Math.max(0, returnDay - this.due) * perDay;
}
}
const loan = new Loan("asha", "111", 0 + 14);
console.log("on time:", loan.fine(14, 10));
console.log("3 days late:", loan.fine(17, 10));
Expected output:
on time: 0
3 days late: 30
The Rules, in Order
Write the borrowing rules as plain checks, in a fixed order, each with a clear reason.
Step through four borrow requests. The checks are: the 3-book limit, a free copy, and whether someone else is first in the queue:
| Member | Books held | Free copies | First in queue | Answer |
|---|---|---|---|---|
| asha | 1 | 2 | none | ok |
| asha | 3 | 2 | none | refused: already has 3 books |
| meena | 0 | 1 | ravi | refused: reserved for ravi |
| ravi | 0 | 1 | ravi | ok |
Here is the same idea in code:
The rules, checked in a clear order
const MAX_LOANS = 3;
function canBorrow(loansHeld: number, freeCopies: number, firstInQueue: string | null, member: string): string {
if (loansHeld >= MAX_LOANS) return "refused: already has 3 books";
if (freeCopies === 0) return "refused: no copy free";
if (firstInQueue !== null && firstInQueue !== member) return `refused: reserved for ${firstInQueue}`;
return "ok";
}
console.log(canBorrow(1, 2, null, "asha"));
console.log(canBorrow(3, 2, null, "asha"));
console.log(canBorrow(0, 1, "ravi", "meena"));
console.log(canBorrow(0, 1, "ravi", "ravi"));
Expected output:
ok
refused: already has 3 books
refused: reserved for ravi
ok
Easy way to remember the design: a Book knows how many copies are free. A Loan knows who has a copy and when it is due. The Library checks the rules and keeps the waiting lists.
The Waiting List
When no copy is free, a member can reserve the book. The waiting list is a queue: the first person to reserve gets the next free copy.
Step through three reservations and one returned copy:
Here is the same idea in code:
A waiting list is a queue: first come, first served
const waiting: string[] = [];
waiting.push("ravi");
waiting.push("meena");
waiting.push("kiran");
console.log("next in line:", waiting[0]);
waiting.shift(); // ravi borrows the book
console.log("now waiting:", waiting.join(", "));
Expected output:
next in line: ravi
now waiting: meena, kiran
A reserved copy must be held for the first person in the queue. If anyone could borrow it when it comes back, the person who waited longest might never get it.
Room to Grow
Teachers may get more books for longer. Instead of if (member.type === "teacher") in many places, keep the rules in one table, or one strategy per member type.
The rules table has one row per member type:
| Member type | Max books | Loan days |
|---|---|---|
| student | 3 | 14 |
| teacher | 10 | 30 |
The Library reads the row for the member, so a new member type is one new row.
Here is the code:
Different members, different rules: a strategy
interface MemberRules {
maxLoans: number;
loanDays: number;
}
const RULES: Record<string, MemberRules> = {
student: { maxLoans: 3, loanDays: 14 },
teacher: { maxLoans: 10, loanDays: 30 },
};
for (const kind of ["student", "teacher"]) {
const r = RULES[kind];
console.log(`${kind}: up to ${r.maxLoans} books for ${r.loanDays} days`);
}
Expected output:
student: up to 3 books for 14 days
teacher: up to 10 books for 30 days
Lab: Build It
Build the Library class. The hidden tests lend and return books, check the limit and fines, and check that the waiting list is fair.
Predict the Output
Common Mistakes
| Mistake | Wrong | Correct | Why it happens |
|---|---|---|---|
| Title as one item | Only one person can borrow Clean Code. | A count of free copies. | It is easy to forget a library has copies. |
| Due day not stored | Working out due days from the borrow date everywhere. | Store due on the loan. | The rule "14 days" seems simple enough to repeat. |
| Unfair queue | A returned copy goes to whoever calls borrow first. | Hold it for the first person waiting. | The happy-path test has no queue. |
| Fine from the borrow date | Charging from day 0 instead of the due day. | Count only days after the due day. | Mixing up the two dates. |
| Vague errors | "cannot borrow" for every reason. | A clear reason for each rule. | One error message is quicker to write. |
Practice
What to Remember
- A book has a count of free copies.
- A loan records who, what and the due day.
- Check the rules in a fixed order, each with a clear reason.
- The waiting list is a queue: first come, first served.
- Keep member rules in a table or strategy.
Further Reading
These are the sources used to check this case study:
- MDN: Array.prototype.shift: taking the first person from a queue.
- Microsoft Learn: Queue: queues in C#.
- TypeScript Handbook: Classes: static readonly constants.
Next: Vending Machine. You will use the State pattern to build a machine whose buttons do different things at different times.