Concepts / Modeling and API shape / From requirements to classes

From requirements to classes

Nouns, verbs and the use-case walk that finds the classes.

01The intuition

The problem it solves

The prompt says: "Design a library. Members borrow books, return them, and pay fines when late." A student who starts typing immediately writes one Library class with every list and every method in it, and a Book with a borrowed_by field. It works for the first two use cases. Then the interviewer says a library owns three copies of the same book, or a member may reserve a book that is out, and the single-class design has nowhere to put either fact without rewriting the methods that already work.

class Library:
    def __init__(self) -> None:
        self.books: list[str] = []
        self.members: list[str] = []
        self.loans: dict[str, str] = {}      # title -> member
        self.fines: dict[str, int] = {}

    def borrow(self, title: str, member: str) -> None:
        if title in self.loans: raise ValueError("out")
        self.loans[title] = member             # three copies? reservations? no room

Build it one piece at a time

Step one: before any code, write the use cases as short sentences with an actor and a verb: a member borrows a copy; a member returns a copy; the system charges a fine when a copy is late; a member reserves a book that has no free copy. Then underline the nouns: member, copy, book, fine, reservation, loan. Nouns that hold changing state or enforce a rule become classes; nouns that are just a value (a title, a date) become fields.

# use cases (written first, no code yet)
# 1. member borrows a copy        -> Member, Copy, Loan
# 2. member returns a copy        -> Loan ends, Copy free again
# 3. late return charges a fine   -> Fine belongs to Member, computed from Loan
# 4. member reserves a book       -> Reservation on Book, not on a Copy

Step two: for each noun, write the one sentence of responsibility and the few fields that follow from it. Book is the title record; Copy is a physical item that can be out; Loan ties a copy to a member with dates; the library is only the place these are looked up. Notice that "three copies" is now natural: one Book, three Copy objects.

from dataclasses import dataclass, field
from datetime import date

@dataclass
class Book:
    isbn: str
    title: str
    copies: list["Copy"] = field(default_factory=list)

@dataclass
class Copy:
    book: Book
    barcode: str
    loan: "Loan | None" = None          # None means on the shelf

@dataclass
class Loan:
    copy: Copy
    member: "Member"
    due: date

Step three: walk each use case through the classes and give the verb to the object that owns the data it changes. Borrowing changes a copy and creates a loan, so it lives where copies are found; the fine is computed from a loan, so the loan knows how late it is.

class Member:
    def __init__(self, name: str) -> None:
        self.name = name
        self.loans: list[Loan] = []
        self.fines: int = 0

class Loan:
    def days_late(self, today: date) -> int:
        return max(0, (today - self.due).days)

class Catalogue:
    def borrow(self, book: Book, member: Member, today: date) -> Loan:
        free = next((c for c in book.copies if c.loan is None), None)
        if free is None:
            raise NoCopyFree(book.isbn)
        loan = Loan(free, member, today + LOAN_PERIOD)
        free.loan = loan; member.loans.append(loan)
        return loan

Why it works

The principle: classes come from the problem, not from the solution. Each use case is a path through a few objects, and a design is good when every path reads naturally and every rule has exactly one home. The invariant to check is that each piece of state is owned by one class and changed through its methods only. Where it breaks: a class named after a verb (BorrowManager) that holds no state, a noun turned into a class when it was only a value (Title), and the "system" noun that collects every method because nobody asked who owns what. Interviewers test by adding a requirement mid-way; if it lands in one class with a small change, the nouns were right.

The trade-off you must say out loud

Spending five of forty-five minutes on sentences feels slow, and it is possible to over-model: not every noun deserves a class, and a reservation might be a list of members on the book until a rule needs more. Say that you start with the fewest classes the use cases need and let the interviewer's extensions pull new ones out. The usual follow-up is a new use case; answer it by walking it through the existing objects before adding anything.

Where it shows up in the 100 Google problems

  • Library management system: the example above with holds and fines completed.
  • Parking lot: lot, floor, spot, vehicle, ticket come straight out of the park-and-leave use cases.
  • Restaurant waitlist: party, table and the waitlist itself; the use cases decide whether a party or a person is the unit.
  • Splitwise: group, expense, share and balance, where the balance is derived rather than stored.
  • Hotel booking system: room type versus room, the same split as book versus copy.

02Used in these problems