Facebook Pixel

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.

Invest in Yourself
Your new job is waiting. 83% of people that complete the program get a job offer. Unlock unlimited access to all content and features.
Go Pro