Classes & Objects
A class is a blueprint: it describes what data a thing holds and what it can do. An object is a running instance of that blueprint — the actual shopping cart for customer 7832, not the specification of what a cart is. Every object created from the same class gets its own private copy of the state the class defines, but all objects share the same set of methods.
The distinction matters in interviews because it shapes how you talk about design. When you say
"a Cart has a list of line items and a total," you are describing the class. When you say "the
cart for session 7832 currently holds three items totalling $47.50," you are describing an object.
Candidates who blur these levels often end up with classes that are either too generic to be
useful or too specific to reuse.
What belongs in a class
A class should own the data it needs to do its job and the operations that directly operate on that data. If a piece of logic reads or changes a field on the class, that logic almost certainly belongs as a method on the class rather than as a function somewhere else.
Consider a shopping cart. It holds line items and applies promotions. Whether a discount is valid, how the subtotal is computed, whether the cart is empty — all of these decisions depend on the cart's internal state, so they belong on the cart itself. The moment you see external code reaching into the cart's fields to compute these things, the design has a problem.
Cart owns the computation of subtotal() because it has all the data required. LineItem
owns line_total() for the same reason. Neither asks an external helper for those answers.
The anemic class smell
A common mistake is the anemic class: a class that is nothing but a bag of public fields, with all the real work living in a separate "manager" or "service" object. Data sits in one place and the rules in another, so every rule must reach across to read the data.
Below is what an anemic Cart looks like, followed by a version where the class owns its
behavior.
1# Bad: Cart is a passive data bag — CartService has all the logic,
2# so every caller must know how to manipulate Cart's internals.
3class Cart:
4 def __init__(self):
5 self.items = [] # public and mutable
6 self.coupon = None # callers can set anything here
7
8class CartService:
9 def add_item(self, cart, product, qty):
10 cart.items.append({"product": product, "qty": qty})
11
12 def subtotal(self, cart):
13 total = sum(i["product"].price * i["qty"] for i in cart.items)
14 if cart.coupon:
15 total -= cart.coupon.discount
16 return total
1from dataclasses import dataclass, field
2from enum import Enum, auto
3
4@dataclass
5class Product:
6 id: str
7 name: str
8 price: float
9
10@dataclass
11class LineItem:
12 product: Product
13 quantity: int
14
15 def line_total(self) -> float:
16 return self.product.price * self.quantity
17
18@dataclass
19class Coupon:
20 code: str
21 discount: float
22
23# Good: Cart owns its state and the operations that depend on it.
24class Cart:
25 def __init__(self) -> None:
26 self._items: list[LineItem] = []
27 self._coupon: Coupon | None = None
28
29 def add_item(self, product: Product, quantity: int) -> None:
30 for item in self._items:
31 if item.product.id == product.id:
32 item.quantity += quantity
33 return
34 self._items.append(LineItem(product, quantity))
35
36 def remove_item(self, product_id: str) -> None:
37 self._items = [i for i in self._items if i.product.id != product_id]
38
39 def apply_coupon(self, coupon: Coupon) -> None:
40 self._coupon = coupon
41
42 def subtotal(self) -> float:
43 total = sum(i.line_total() for i in self._items)
44 if self._coupon:
45 total -= self._coupon.discount
46 return max(total, 0.0)
47
48 def is_empty(self) -> bool:
49 return len(self._items) == 0
State vs behavior
Every class has two faces: state (the fields) and behavior (the methods). State answers "what does this object remember?" and behavior answers "what can this object do?" A healthy class keeps the two in balance — the state exists to support the behavior, and the behavior only reaches into the state it actually needs.
The rule of thumb is to ask: "Could this method be moved to another class and still work the
same way?" If the answer is yes, it probably belongs there. Cart.subtotal() cannot move to
Product because it reads across multiple line items and checks the coupon — both of which live
on Cart. That is a strong signal the method belongs where it is.
Interview framing
When sketching a design, each class introduced should have a reason to exist beyond "it stores data for another class." A useful question is: what does this class decide? What can it refuse to do? A class that makes no decisions and enforces no rules is functionally a struct — the equivalent of a plain data record with no behavior attached.
The shopping cart above is a clean example: Cart decides whether a coupon is valid to apply,
whether a product already exists in the list, and what the floor of the subtotal is. Those
decisions belong with the data that informs them, and nowhere else.
Entity vs Value Object
Two objects of the same class are not always meaningfully different, and the distinction turns
on identity. An entity has an identity that persists even as its fields change: a bank
account stays the same account whether its balance is $0 or $5,000, and two accounts with
identical balances are still distinct because each carries its own id. A value object has no
identity of its own — it is defined entirely by its field values, so Money(amount=100, currency="USD") is interchangeable with any other Money holding the same amount and currency,
the way Point(2, 3) is the same point wherever it appears.
This difference drives how the objects behave. A value object is normally immutable and compared
by its fields, so to "change" a price you create a new Money rather than mutating the old one,
which makes it safe to share freely. An entity is compared by identity and changes state over its
lifetime: a reservation moves from requested to confirmed to cancelled while remaining the same
reservation. Decide this for each class as you model a problem. A Reservation or an Account
is an entity; a TimeSlot, a Money, or a Coupon is a value object. The choice tells you
whether to give the class a stable id and mutable state, or to make it immutable and equatable
by value.