Build a Library Management System with Python

Apply Python OOP concepts by building a complete library management system with classes for books, members, loans, and the library itself.

8 min read

Building a library management system with Python is a project that brings together every major object-oriented programming concept from this section into a single working application. You will design classes that represent the real-world entities in a library: books that can be checked out, members who borrow them, loans that track who has what, and the library itself that orchestrates the entire system. By the end of this project, you will have applied inheritance, composition, encapsulation, and method design to a problem that is complex enough to be realistic but small enough to complete in a single session. The project is not about building a production-ready library system with a database backend and a web interface. It is about practicing the skill of translating a set of requirements into a set of classes that work together.

The library system models four core entities. A Book has a title, an author, a unique identifier, and a status indicating whether it is available or checked out. A Member has a name, a member ID, and a list of books they currently have on loan. A Loan connects a book to a member for a specific period, tracking when the book was borrowed and when it is due back. The Library aggregates books and members and provides the operations that library staff would perform: adding new books, registering members, checking out books, returning books, and searching the collection.

The design uses composition for most relationships because a library has books and members rather than being a kind of book or member. Inheritance appears in one place: different types of books, like fiction, non-fiction, and reference, share a common base class but have type-specific behavior, such as reference books that cannot be checked out. This blend of composition and inheritance reflects the patterns covered in the articles on composition versus inheritance and single inheritance.

Designing the Book class hierarchy

The Book class is the foundation of the system. Every book has a title, an author, an ISBN, and a status. The status starts as "available" and changes to "checked out" when a member borrows the book. The base class defines the shared interface and the common behavior, and child classes add type-specific rules:

pythonpython
class Book:
    def __init__(self, title, author, isbn):
        self.title = title
        self.author = author
        self.isbn = isbn
        self._status = "available"
 
    def checkout(self):
        if self._status == "available":
            self._status = "checked out"
            return True
        return False

The checkout method returns a boolean so the Library can verify success. Reference books override checkout to always refuse. Two small members round out the class: return_book resets the status back to "available", and the is_available property reads self._status == "available" so callers never need to know the exact string values the status field uses internally.

The Member and Loan classes

pythonpython
class Member:
    def __init__(self, name, member_id):
        self.name = name
        self.member_id = member_id
        self._loans = []
 
    def borrow(self, book):
        if book.checkout():
            loan = Loan(book, self)
            self._loans.append(loan)
            return loan
        return None

The borrow method checks out the book first, and only records a loan if the checkout succeeds, which prevents a member from borrowing a book that is already unavailable. Returning a book requires finding the matching active loan and closing it:

pythonpython
    def return_book(self, book):
        for loan in self._loans:
            if loan.book == book and loan.is_active:
                loan.close()
                book.return_book()
                return True
        return False

The Member delegates checkout and return logic to Book and Loan, keeping its own methods focused on coordination. Each loan is tracked in the internal list.

pythonpython
from datetime import datetime, timedelta
 
class Loan:
    def __init__(self, book, member, duration_days=14):
        self.book = book
        self.member = member
        self.borrow_date = datetime.now()
        self.due_date = self.borrow_date + timedelta(days=duration_days)
        self.return_date = None
    def close(self):
        self.return_date = datetime.now()
    @property
    def is_active(self):
        return self.return_date is None

The Loan constructor records timestamps automatically. The close method and is_active property manage the loan lifecycle without exposing internal date handling to callers. This design encapsulates all time-related logic inside the Loan class, so the rest of the system never needs to import datetime or calculate due dates directly. When a book is returned, the Member class calls loan.close() and the timestamp is recorded without the caller knowing how dates are stored or formatted.

The Library class

pythonpython
class Library:
    def __init__(self, name):
        self.name = name
        self._books = {}
        self._members = {}
    def add_book(self, book):
        self._books[book.isbn] = book
    def register_member(self, member):
        self._members[member.member_id] = member

The Library stores books and members in dictionaries keyed by ISBN and member ID for fast lookup. Adding and registering are straightforward insertions. The checkout and return operations delegate to Member:

pythonpython
    def checkout_book(self, isbn, member_id):
        book = self._books.get(isbn)
        member = self._members.get(member_id)
        if not book or not member:
            return None
        return member.borrow(book)
    def return_book(self, isbn, member_id):
        book = self._books.get(isbn)
        member = self._members.get(member_id)
        if not book or not member:
            return False
        return member.return_book(book)

The Library coordinates books and members through dictionary lookups, delegating all domain logic to the Book and Member classes. This separation keeps each class focused on its own responsibility: the Library knows where to find things, the Book knows its own availability, the Member knows what they have borrowed, and the Loan knows when items are due. No class reaches into another's internal attributes, which means each can change its internal representation without affecting the others.

Rune AI

Rune AI

Key Insights

  • The library system uses composition for the Library's relationship with Books and Members, and inheritance only for genuine is-a relationships like book types.
  • Encapsulation keeps internal data like loan records private behind underscore-prefixed attributes and public methods.
  • The Book base class defines the shared interface, and child classes like FictionBook and ReferenceBook add specialized behavior.
  • Loan management tracks which member has which book and enforces business rules like return deadlines.
  • Building a complete project reinforces how individual OOP concepts combine into a working application.
RunePowered by Rune AI

Frequently Asked Questions

Is this library management system suitable for a beginner Python project?

This project assumes you have completed the earlier articles in this OOP section and are comfortable with classes, inheritance, and basic file handling. If you have worked through the section in order, you have all the skills needed. The project applies concepts you already know rather than introducing new ones, which makes it a good test of whether you can combine individual OOP skills into a working application.

Can I extend this project after completing the tutorial?

Yes, the system is designed to be extended. You could add a reservation system for books that are currently on loan, implement fine calculation for overdue books, add a search feature that finds books by author or title substring, replace the in-memory storage with file or database persistence, or build a command-line or graphical interface on top of the class layer. The core class design should accommodate these extensions without major restructuring.

Why is the library system built with composition instead of deep inheritance?

The system uses composition because the relationships are mostly has-a rather than is-a. A Library has Books and Members rather than being a kind of Book or Member. The only inheritance used is for different book types, where the relationship is genuinely is-a. This design follows the principle of favoring composition over inheritance, which keeps the classes loosely coupled and easy to modify independently.

Conclusion

Building a library management system applies every major OOP concept from this section: classes and objects, constructors, attributes and methods, inheritance for book types, encapsulation for internal state, and composition for the library's relationship with its books and members. The project is small enough to complete in a few hours but real enough to demonstrate how object-oriented design turns a set of requirements into a working application.