Machine Coding Problem

Library Management System

maco30maco60macoAllutilitysearch-&-strategy-penalty-logic
Commonly Asked By:AmazonMicrosoftGoogle

Requirements & System Scope

Functional Scope (In-Scope)

  • Dynamic Book Search: Filter books by Title, Author, or ISBN index configurations.
  • Checkout and Return: Support checkout and return pipelines including automatic penalty fine audits.
  • FIFO Book Reservations: Per-book reservation queues holding copies for waiting list members in FIFO order.
  • Outstanding Fine Blocks: Block member book checkouts if total unpaid fines exceed configurable limits (e.g. $50).

Explicit Boundaries (Out-of-Scope)

  • No Real Physical Locker Interfacing: Hardware scanners, shelf location tags, or physical locker triggers are mocked behind clean API classes.
  • No External Payment Gateway: Overdue fine payments are resolved internally on account sheets rather than integrating with card processors.

Class Diagram & Entity Relationships

Structural blueprint showing models and lending associations:

Loading...
  • Polymorphic Book Statuses: BookStatus tracks active book states (AVAILABLE, LOANED, RESERVED) to streamline checkout checks.
  • Self-Contained Loans: Loan encapsulates borrow parameters (issue date, due date, fine rates) to prevent logical pollution of member files.

Design Patterns & SOLID Principles

  • Strategy Pattern (Fine Policy): Decouples dynamic penalty calculators (standard flat rate vs. premium tiered multipliers) from checkout logs, satisfying the Open-Closed Principle (OCP).
  • Lazy Penalty Calculator Pattern: Instead of running background cron schedulers to increment fines daily, overdue penalties are resolved lazily on book return actions, preventing redundant CPU cycles.
  • Single Responsibility Principle (SRP):Book tracks individual cell parameters. Loan computes overdue date penalties. LibraryService controls checkout operations.

Core Execution Workflows

Checkout Pipeline

  1. Member triggers checkoutBook(memberId, isbn).
  2. Check member fine standing (block if > $50).
  3. Acquire exclusive book lock:
    1. If book is AVAILABLE, set status to LOANED.
    2. If book is RESERVED, poll reservation queue. If next member matches requesting member, set status to LOANED; else return false.
  4. Build new Loan, add to member record list, release lock, and return true.

Concurrency & Thread Safety Strategy

When multiple members attempt to check out the final available copy of a popular book simultaneously, race conditions can cause double lending:

  • Fine-Grained Monitor Locks: Wrap status validations and reservation queue polling in synchronized blocks or thread Mutexes per-book, allowing operations on different books to run in parallel.
  • Concurrent Collections: House system directories in thread-safe ConcurrentHashMap to prevent state leaks.

Complete Clean Code Blueprint

Highly robust reference designs in Java and Python:

// โ”€โ”€โ”€ JAVA BLUEPRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.*;
import java.util.concurrent.*;

enum BookStatus { AVAILABLE, LOANED, RESERVED }

interface PenaltyStrategy {
    double calculateFine(long daysOverdue);
}

class StandardPenaltyStrategy implements PenaltyStrategy {
    private final double dailyRate;
    public StandardPenaltyStrategy(double dailyRate) { this.dailyRate = dailyRate; }
    @Override
    public double calculateFine(long daysOverdue) {
        return daysOverdue * dailyRate;
    }
}

class PremiumPenaltyStrategy implements PenaltyStrategy {
    private final double dailyRate;
    public PremiumPenaltyStrategy(double dailyRate) { this.dailyRate = dailyRate; }
    @Override
    public double calculateFine(long daysOverdue) {
        if (daysOverdue <= 5) {
            return daysOverdue * dailyRate;
        }
        return (5 * dailyRate) + ((daysOverdue - 5) * dailyRate * 2.0); // Double fine after 5 days
    }
}

class Book {
    private final String isbn;
    private final String title;
    private final String author;
    private BookStatus status = BookStatus.AVAILABLE;
    private final Queue<String> reservationQueue = new LinkedList<>();

    public Book(String isbn, String title, String author) {
        this.isbn = isbn;
        this.title = title;
        this.author = author;
    }

    public String getIsbn() { return isbn; }
    public String getTitle() { return title; }
    public String getAuthor() { return author; }
    public synchronized BookStatus getStatus() { return status; }
    public synchronized void setStatus(BookStatus status) { this.status = status; }

    public synchronized void reserveMember(String memberId) {
        reservationQueue.add(memberId);
        if (status == BookStatus.AVAILABLE) {
            status = BookStatus.RESERVED;
        }
    }

    public synchronized String pollReservation() {
        return reservationQueue.poll();
    }

    public synchronized boolean hasReservations() {
        return !reservationQueue.isEmpty();
    }
}

class Loan {
    private final String bookIsbn;
    private final String memberId;
    private final long issueDate;
    private final long dueDate;
    private long returnDate = 0;

    public Loan(String bookIsbn, String memberId, long issueDate, long dueDate) {
        this.bookIsbn = bookIsbn;
        this.memberId = memberId;
        this.issueDate = issueDate;
        this.dueDate = dueDate;
    }

    public String getBookIsbn() { return bookIsbn; }
    public String getMemberId() { return memberId; }
    public long getDueDate() { return dueDate; }
    public void setReturnDate(long returnDate) { this.returnDate = returnDate; }

    public double calculateFine(long now, PenaltyStrategy strategy) {
        long effectiveEnd = (returnDate > 0) ? returnDate : now;
        if (effectiveEnd > dueDate) {
            long daysOverdue = (effectiveEnd - dueDate) / (1000 * 60 * 60 * 24);
            return strategy.calculateFine(daysOverdue);
        }
        return 0.0;
    }
}

class Member {
    private final String id;
    private final String name;
    private final List<Loan> activeLoans = new ArrayList<>();
    private double unpaidFines = 0.0;

    public Member(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getId() { return id; }
    public synchronized List<Loan> getActiveLoans() { return new ArrayList<>(activeLoans); }
    public synchronized void addLoan(Loan loan) { activeLoans.add(loan); }
    public synchronized void removeLoan(Loan loan) { activeLoans.remove(loan); }
    public synchronized double getUnpaidFines() { return unpaidFines; }
    public synchronized void addFine(double amount) { unpaidFines += amount; }
}

class LibraryService {
    private final Map<String, Book> books = new ConcurrentHashMap<>();
    private final Map<String, Member> members = new ConcurrentHashMap<>();
    private final PenaltyStrategy penaltyStrategy;

    public LibraryService(PenaltyStrategy penaltyStrategy) {
        this.penaltyStrategy = penaltyStrategy;
    }

    public void addBook(Book book) { books.put(book.getIsbn(), book); }
    public void addMember(Member member) { members.put(member.getId(), member); }

    public boolean checkoutBook(String memberId, String isbn) {
        Book book = books.get(isbn);
        Member member = members.get(memberId);
        if (book == null || member == null) return false;

        if (member.getUnpaidFines() > 50.0) {
            System.out.println("Checkout rejected: Member " + member.getId() + " has excess fines of $" + member.getUnpaidFines());
            return false;
        }

        synchronized (book) {
            if (book.getStatus() == BookStatus.AVAILABLE) {
                book.setStatus(BookStatus.LOANED);
            } else if (book.getStatus() == BookStatus.RESERVED) {
                String nextInLine = book.pollReservation();
                if (memberId.equals(nextInLine)) {
                    book.setStatus(BookStatus.LOANED);
                } else {
                    return false;
                }
            } else {
                return false;
            }

            long now = System.currentTimeMillis();
            long dueDate = now + (14L * 24 * 60 * 60 * 1000);
            Loan loan = new Loan(isbn, memberId, now, dueDate);
            member.addLoan(loan);
            System.out.println("Checkout successful: " + book.getTitle() + " lent to Member " + member.getId());
            return true;
        }
    }

    public void reserveBook(String memberId, String isbn) {
        Book book = books.get(isbn);
        Member member = members.get(memberId);
        if (book == null || member == null) return;

        book.reserveMember(memberId);
        System.out.println("Reservation successful: " + book.getTitle() + " reserved by Member " + member.getId());
    }

    public void returnBook(String memberId, String isbn, long returnTimeOverride) {
        Book book = books.get(isbn);
        Member member = members.get(memberId);
        if (book == null || member == null) return;

        Loan targetLoan = null;
        for (Loan l : member.getActiveLoans()) {
            if (l.getBookIsbn().equals(isbn)) {
                targetLoan = l;
                break;
            }
        }

        if (targetLoan == null) return;

        targetLoan.setReturnDate(returnTimeOverride);
        double fine = targetLoan.calculateFine(returnTimeOverride, penaltyStrategy);
        if (fine > 0) {
            member.addFine(fine);
            System.out.println("Overdue Book returned! Fine calculated: $" + fine);
        } else {
            System.out.println("Book returned in time. No fine.");
        }

        member.removeLoan(targetLoan);

        synchronized (book) {
            if (book.hasReservations()) {
                book.setStatus(BookStatus.RESERVED);
            } else {
                book.setStatus(BookStatus.AVAILABLE);
            }
        }
    }
}

public class LibraryDriver {
    public static void main(String[] args) {
        System.out.println("=== LIBRARY MANAGEMENT SYSTEM SIMULATION ===");
        PenaltyStrategy strategy = new PremiumPenaltyStrategy(2.0); // $2.0 per day, double after 5 days
        LibraryService library = new LibraryService(strategy);

        Book b1 = new Book("ISBN-11", "Clean Architecture", "Robert C. Martin");
        library.addBook(b1);

        Member m1 = new Member("MEM-1", "Alice");
        Member m2 = new Member("MEM-2", "Bob");
        library.addMember(m1);
        library.addMember(m2);

        // Checkout success
        library.checkoutBook("MEM-1", "ISBN-11");

        // Bob reserves the book while Alice has it
        library.reserveBook("MEM-2", "ISBN-11");

        // Alice returns it overdue by 8 days
        long now = System.currentTimeMillis();
        long eightDaysLater = now + (22L * 24 * 60 * 60 * 1000); // 14 days due + 8 days late
        library.returnBook("MEM-1", "ISBN-11", eightDaysLater);
        System.out.println("Alice unpaid fines: $" + m1.getUnpaidFines()); // 5 * 2 + 3 * 4 = $22.0
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...