Class Diagrams in UML: Notation & Examples

Updated 2026-03-11

TL;DR

Class diagrams are UML’s most widely-used diagram type, showing the static structure of a system through classes, their attributes, methods, and relationships. They serve as blueprints for implementation and communication tools between developers, making them essential for design discussions and technical interviews.

Prerequisites: Understanding of classes and objects, basic knowledge of OOP principles (encapsulation, inheritance, polymorphism), familiarity with access modifiers (public, private, protected), and understanding of relationships between classes (has-a, is-a).

After this topic: Draw class diagrams from code or requirements, identify and represent different relationship types (association, aggregation, composition, inheritance, dependency), translate class diagrams into working code, and use class diagrams to communicate design decisions in technical discussions and interviews.

Core Concept

What is a Class Diagram?

A class diagram is a structural UML diagram that visualizes the classes in a system and how they relate to each other. Think of it as an architectural blueprint — it shows what exists in your system, not how it behaves over time.

Each class is represented as a box divided into three compartments:

  1. Class name (top): The identifier for the class
  2. Attributes (middle): The data/fields the class holds
  3. Methods (bottom): The operations/functions the class can perform

Why Class Diagrams Matter

Class diagrams bridge the gap between design and implementation. Before writing a single line of code, you can:

  • Visualize the entire system structure
  • Identify missing classes or redundant relationships
  • Communicate design decisions to team members
  • Spot design flaws early (tight coupling, circular dependencies)

In interviews, you’ll often be asked to “design a parking lot” or “design a library system.” Interviewers expect you to sketch a class diagram first, showing you can think before coding.

Core Components

Visibility modifiers indicate access levels:

  • + public: accessible everywhere
  • - private: accessible only within the class
  • # protected: accessible in class and subclasses
  • ~ package/default: accessible within the same package

Attributes are shown as: visibility name: type = defaultValue

Example: - balance: float = 0.0

Methods are shown as: visibility name(parameters): returnType

Example: + deposit(amount: float): bool

Static members are underlined. Abstract classes/methods are italicized or marked with {abstract}.

Visual Guide

Basic Class Structure

classDiagram
    class BankAccount {
        -accountNumber: string
        -balance: float
        -owner: string
        +deposit(amount: float) bool
        +withdraw(amount: float) bool
        +getBalance() float
    }
    note for BankAccount "Top: Class name\nMiddle: Attributes (data)\nBottom: Methods (behavior)"

A class box has three compartments: name, attributes, and methods. The minus sign (-) indicates private members, while plus (+) indicates public members.

Inheritance Relationship

classDiagram
    Animal <|-- Dog
    Animal <|-- Cat
    class Animal {
        #name: string
        #age: int
        +makeSound() void
        +eat() void
    }
    class Dog {
        -breed: string
        +makeSound() void
        +fetch() void
    }
    class Cat {
        -indoor: bool
        +makeSound() void
        +scratch() void
    }

Inheritance is shown with a solid line and hollow triangle pointing to the parent class. Dog and Cat inherit from Animal, gaining access to protected (#) and public (+) members.

Association vs Aggregation vs Composition

classDiagram
    Professor --> Course : teaches
    Department o-- Professor : has
    University *-- Department : contains
    class Professor {
        -name: string
        +teach() void
    }
    class Course {
        -courseCode: string
    }
    class Department {
        -name: string
    }
    class University {
        -name: string
    }

Association (—>): Professor teaches Course (weak relationship). Aggregation (o—): Department has Professors (shared ownership). Composition (—): University contains Departments (strong ownership, Department can’t exist without University).*

Dependency Relationship

classDiagram
    OrderProcessor ..> PaymentGateway : uses
    OrderProcessor ..> EmailService : uses
    class OrderProcessor {
        +processOrder(order: Order) bool
    }
    class PaymentGateway {
        +charge(amount: float) bool
    }
    class EmailService {
        +sendConfirmation(email: string) void
    }

Dependency (..>): OrderProcessor uses PaymentGateway and EmailService temporarily (as method parameters or local variables), but doesn’t store them as attributes.

Multiplicity in Relationships

classDiagram
    Customer "1" --> "0..*" Order : places
    Order "1" *-- "1..*" OrderItem : contains
    OrderItem "*" --> "1" Product : references
    class Customer {
        -customerId: string
        -name: string
    }
    class Order {
        -orderId: string
        -date: datetime
    }
    class OrderItem {
        -quantity: int
        -price: float
    }
    class Product {
        -productId: string
        -name: string
    }

Multiplicity shows how many instances participate in a relationship. 1 = exactly one, 0.. = zero or more, 1..* = one or more, * = many. One Customer places zero or more Orders; one Order contains one or more OrderItems.*

Examples

Example 1: Simple Library System

Let’s design a basic library system with books, members, and loans.

Class Diagram (text representation):

┌─────────────────┐
│     Book        │
├─────────────────┤
│ - isbn: string  │
│ - title: string │
│ - author: string│
├─────────────────┤
│ + getDetails()  │
└─────────────────┘

        │ (composition)
        │ 1..*
┌─────────────────┐
│    Library      │
├─────────────────┤
│ - name: string  │
│ - books: list   │
├─────────────────┤
│ + addBook()     │
│ + findBook()    │
└─────────────────┘

        │ (association)
        │ 0..*
┌─────────────────┐
│     Loan        │
├─────────────────┤
│ - loanDate: date│
│ - dueDate: date │
├─────────────────┤
│ + isOverdue()   │
└─────────────────┘

        │ (association)
        │ 1
┌─────────────────┐
│    Member       │
├─────────────────┤
│ - memberId: str │
│ - name: string  │
├─────────────────┤
│ + borrowBook()  │
└─────────────────┘

Python Implementation:

from datetime import datetime, timedelta
from typing import List

class Book:
    def __init__(self, isbn: str, title: str, author: str):
        self.__isbn = isbn  # Private attribute
        self.__title = title
        self.__author = author
    
    def get_details(self) -> str:
        return f"{self.__title} by {self.__author} (ISBN: {self.__isbn})"

class Member:
    def __init__(self, member_id: str, name: str):
        self.__member_id = member_id
        self.__name = name
        self.__active_loans: List['Loan'] = []
    
    def borrow_book(self, book: Book) -> 'Loan':
        loan = Loan(self, book)
        self.__active_loans.append(loan)
        return loan
    
    def get_name(self) -> str:
        return self.__name

class Loan:
    def __init__(self, member: Member, book: Book):
        self.__member = member  # Association: Loan knows about Member
        self.__book = book      # Association: Loan knows about Book
        self.__loan_date = datetime.now()
        self.__due_date = self.__loan_date + timedelta(days=14)
    
    def is_overdue(self) -> bool:
        return datetime.now() > self.__due_date
    
    def get_details(self) -> str:
        status = "OVERDUE" if self.is_overdue() else "Active"
        return f"{self.__member.get_name()} borrowed {self.__book.get_details()} - {status}"

class Library:
    def __init__(self, name: str):
        self.__name = name
        self.__books: List[Book] = []  # Composition: Library owns Books
    
    def add_book(self, book: Book) -> None:
        self.__books.append(book)
    
    def find_book(self, isbn: str) -> Book:
        for book in self.__books:
            if book._Book__isbn == isbn:  # Name mangling to access private
                return book
        return None

# Usage
library = Library("City Library")
book1 = Book("978-0132350884", "Clean Code", "Robert Martin")
library.add_book(book1)

member = Member("M001", "Alice Johnson")
loan = member.borrow_book(book1)

print(loan.get_details())
# Output: Alice Johnson borrowed Clean Code by Robert Martin (ISBN: 978-0132350884) - Active

Key Relationships:

  • Composition (Library *— Book): Library owns books. If library is destroyed, books in the system are too.
  • Association (Member — Loan — Book): Loan connects Member and Book, but they can exist independently.

Try it yourself: Add a return_book() method to Member that removes a loan from active_loans.


Example 2: E-commerce Order System with Inheritance

Class Diagram Relationships:

        Payment (abstract)

            │ (inheritance)
      ┌─────┴─────┐
      │           │
CreditCard    PayPal

    Customer
      │ 1
      │ (association)
      │ 0..*
    Order
      │ 1
      │ (composition)
      │ 1..*
   OrderItem
      │ *
      │ (association)
      │ 1
   Product

Python Implementation:

from abc import ABC, abstractmethod
from typing import List

class Product:
    def __init__(self, product_id: str, name: str, price: float):
        self.product_id = product_id
        self.name = name
        self.price = price

class OrderItem:
    def __init__(self, product: Product, quantity: int):
        self.__product = product  # Association
        self.__quantity = quantity
    
    def get_subtotal(self) -> float:
        return self.__product.price * self.__quantity
    
    def get_details(self) -> str:
        return f"{self.__product.name} x{self.__quantity} = ${self.get_subtotal():.2f}"

class Order:
    def __init__(self, order_id: str):
        self.order_id = order_id
        self.__items: List[OrderItem] = []  # Composition: Order owns OrderItems
    
    def add_item(self, item: OrderItem) -> None:
        self.__items.append(item)
    
    def get_total(self) -> float:
        return sum(item.get_subtotal() for item in self.__items)
    
    def get_summary(self) -> str:
        summary = f"Order {self.order_id}:\n"
        for item in self.__items:
            summary += f"  {item.get_details()}\n"
        summary += f"Total: ${self.get_total():.2f}"
        return summary

class Customer:
    def __init__(self, customer_id: str, name: str, email: str):
        self.customer_id = customer_id
        self.name = name
        self.email = email
        self.__orders: List[Order] = []  # Association: Customer has Orders
    
    def place_order(self, order: Order) -> None:
        self.__orders.append(order)
    
    def get_order_history(self) -> List[Order]:
        return self.__orders.copy()

# Abstract base class for payment methods
class Payment(ABC):
    def __init__(self, amount: float):
        self._amount = amount  # Protected attribute
    
    @abstractmethod
    def process_payment(self) -> bool:
        """Process the payment. Returns True if successful."""
        pass
    
    @abstractmethod
    def get_receipt(self) -> str:
        """Generate payment receipt."""
        pass

class CreditCardPayment(Payment):
    def __init__(self, amount: float, card_number: str, cvv: str):
        super().__init__(amount)
        self.__card_number = card_number[-4:]  # Store only last 4 digits
        self.__cvv = cvv
    
    def process_payment(self) -> bool:
        # Simulate payment processing
        print(f"Processing credit card payment of ${self._amount:.2f}...")
        return True
    
    def get_receipt(self) -> str:
        return f"Credit Card (****{self.__card_number}): ${self._amount:.2f}"

class PayPalPayment(Payment):
    def __init__(self, amount: float, email: str):
        super().__init__(amount)
        self.__email = email
    
    def process_payment(self) -> bool:
        print(f"Processing PayPal payment of ${self._amount:.2f}...")
        return True
    
    def get_receipt(self) -> str:
        return f"PayPal ({self.__email}): ${self._amount:.2f}"

# Usage
product1 = Product("P001", "Laptop", 999.99)
product2 = Product("P002", "Mouse", 29.99)

order = Order("ORD-12345")
order.add_item(OrderItem(product1, 1))
order.add_item(OrderItem(product2, 2))

customer = Customer("C001", "Bob Smith", "bob@example.com")
customer.place_order(order)

print(order.get_summary())
# Output:
# Order ORD-12345:
#   Laptop x1 = $999.99
#   Mouse x2 = $59.98
# Total: $1059.97

payment = CreditCardPayment(order.get_total(), "1234567890123456", "123")
if payment.process_payment():
    print(payment.get_receipt())
# Output:
# Processing credit card payment of $1059.97...
# Credit Card (****3456): $1059.97

Key Design Decisions:

  • Inheritance: Payment is abstract, forcing CreditCardPayment and PayPalPayment to implement process_payment() and get_receipt()
  • Composition: Order owns OrderItems (if order is deleted, items are too)
  • Association: OrderItem references Product (product exists independently)

Java Note: In Java, you’d use abstract class Payment or interface Payment. Python uses ABC (Abstract Base Class) module.

Try it yourself: Create a DebitCardPayment class that inherits from Payment and adds a PIN verification method.


Example 3: Reading a Class Diagram and Implementing It

You’re given this class diagram in an interview:

┌──────────────────┐
│   Vehicle        │ (abstract)
├──────────────────┤
│ # make: string   │
│ # model: string  │
│ # year: int      │
├──────────────────┤
│ + start(): void  │ (abstract)
│ + stop(): void   │
│ + getInfo(): str │
└──────────────────┘


   ┌────┴────┐
   │         │
┌──────┐  ┌──────┐
│ Car  │  │ Truck│
├──────┤  ├──────┤
│-doors│  │-cargo│
│ :int │  │ :int │
├──────┤  ├──────┤
│start │  │start │
│load  │  │load  │
└──────┘  └──────┘

   Fleet
    │ 1
    │ (aggregation)
    │ 0..*
  Vehicle

Implementation:

from abc import ABC, abstractmethod
from typing import List

class Vehicle(ABC):
    def __init__(self, make: str, model: str, year: int):
        self._make = make      # Protected (# in UML)
        self._model = model
        self._year = year
    
    @abstractmethod
    def start(self) -> None:
        pass
    
    def stop(self) -> None:
        print(f"{self._make} {self._model} stopped.")
    
    def get_info(self) -> str:
        return f"{self._year} {self._make} {self._model}"

class Car(Vehicle):
    def __init__(self, make: str, model: str, year: int, doors: int):
        super().__init__(make, model, year)
        self.__doors = doors  # Private (- in UML)
    
    def start(self) -> None:
        print(f"Car {self._make} {self._model} started with key.")
    
    def load_passengers(self, count: int) -> None:
        max_passengers = self.__doors * 2  # Rough estimate
        if count <= max_passengers:
            print(f"Loaded {count} passengers.")
        else:
            print(f"Cannot load {count} passengers. Max: {max_passengers}")

class Truck(Vehicle):
    def __init__(self, make: str, model: str, year: int, cargo_capacity: int):
        super().__init__(make, model, year)
        self.__cargo_capacity = cargo_capacity
    
    def start(self) -> None:
        print(f"Truck {self._make} {self._model} started with ignition.")
    
    def load_cargo(self, weight: int) -> bool:
        if weight <= self.__cargo_capacity:
            print(f"Loaded {weight}kg cargo.")
            return True
        else:
            print(f"Cannot load {weight}kg. Capacity: {self.__cargo_capacity}kg")
            return False

class Fleet:
    def __init__(self, name: str):
        self.__name = name
        self.__vehicles: List[Vehicle] = []  # Aggregation: Fleet has Vehicles
    
    def add_vehicle(self, vehicle: Vehicle) -> None:
        self.__vehicles.append(vehicle)
        print(f"Added {vehicle.get_info()} to {self.__name}")
    
    def start_all(self) -> None:
        print(f"Starting all vehicles in {self.__name}:")
        for vehicle in self.__vehicles:
            vehicle.start()

# Usage
fleet = Fleet("Company Fleet")

car = Car("Toyota", "Camry", 2023, 4)
truck = Truck("Ford", "F-150", 2022, 1000)

fleet.add_vehicle(car)
fleet.add_vehicle(truck)

fleet.start_all()
# Output:
# Added 2023 Toyota Camry to Company Fleet
# Added 2022 Ford F-150 to Company Fleet
# Starting all vehicles in Company Fleet:
# Car Toyota Camry started with key.
# Truck Ford F-150 started with ignition.

car.load_passengers(6)
truck.load_cargo(800)
# Output:
# Loaded 6 passengers.
# Loaded 800kg cargo.

Try it yourself: Add a Motorcycle class that inherits from Vehicle and has a has_sidecar: bool attribute.

Common Mistakes

1. Confusing Association, Aggregation, and Composition

Mistake: Using association (plain line) for everything, or not understanding the ownership difference.

Why it matters: These relationships have different lifecycle implications:

  • Association: Independent lifecycles (Student — Course)
  • Aggregation: Shared ownership (Department has Professors, but professors can exist without department)
  • Composition: Strong ownership (House has Rooms, rooms can’t exist without house)

How to avoid: Ask yourself: “If I delete object A, must object B also be deleted?” If yes, it’s composition. If no, it’s association or aggregation.

Example:

# WRONG: Using simple reference for composition
class House:
    def __init__(self):
        self.rooms = []  # Should create rooms, not just reference them

# RIGHT: House creates and owns rooms
class House:
    def __init__(self, num_rooms: int):
        self.__rooms = [Room(f"Room {i}") for i in range(num_rooms)]
    # Rooms are created with house and destroyed with house

2. Overcomplicating Diagrams with Too Much Detail

Mistake: Including every getter/setter, private helper method, or implementation detail in the diagram.

Why it matters: Class diagrams are for communication and design, not documentation. Too much detail obscures the important relationships and makes diagrams unreadable.

How to avoid: Include only public interfaces and key private attributes. Omit obvious getters/setters unless they have special logic. Focus on relationships between classes.

Example:

# TOO DETAILED:
┌─────────────────────────┐
│      BankAccount        │
├─────────────────────────┤
│ - accountNumber: string │
│ - balance: float        │
│ - createdDate: datetime │
│ - lastModified: datetime│
│ - isActive: bool        │
├─────────────────────────┤
│ + getAccountNumber()    │
│ + setAccountNumber()    │
│ + getBalance()          │
│ + setBalance()          │
│ + getCreatedDate()      │
│ + getLastModified()     │
│ + setLastModified()     │
│ + isActive()            │
│ + setActive()           │
│ + deposit(amount)       │
│ + withdraw(amount)      │
│ - validateAmount()      │
│ - updateTimestamp()     │
│ - logTransaction()      │
└─────────────────────────┘

# BETTER:
┌─────────────────────────┐
│      BankAccount        │
├─────────────────────────┤
│ - accountNumber: string │
│ - balance: float        │
├─────────────────────────┤
│ + deposit(amount): bool │
│ + withdraw(amount): bool│
│ + getBalance(): float   │
└─────────────────────────┘

3. Misusing Inheritance Instead of Composition

Mistake: Creating inheritance hierarchies when composition would be clearer (violating “favor composition over inheritance”).

Why it matters: Inheritance creates tight coupling. If you inherit just to reuse code (not because of an “is-a” relationship), you’ll create brittle designs.

How to avoid: Use the “is-a” test. A Car is a Vehicle (inheritance OK). A Car has an Engine (use composition, not inheritance).

Example:

# WRONG: Car inheriting from Engine
class Engine:
    def start(self):
        print("Engine started")

class Car(Engine):  # Car is NOT an Engine!
    def drive(self):
        self.start()
        print("Driving")

# RIGHT: Car has an Engine (composition)
class Engine:
    def start(self):
        print("Engine started")

class Car:
    def __init__(self):
        self.__engine = Engine()  # Composition
    
    def drive(self):
        self.__engine.start()
        print("Driving")

4. Incorrect Multiplicity Notation

Mistake: Placing multiplicity on the wrong end of the relationship or using incorrect notation.

Why it matters: Multiplicity tells you how many instances participate in a relationship. Getting it backwards changes the meaning entirely.

How to avoid: Read the relationship as a sentence. “One Customer places zero or more Orders” means Customer has “1” on its side and Order has “0..*” on its side.

Example:

# WRONG:
Customer "0..*" --> "1" Order
(This reads: "Many customers place one order" - incorrect!)

# RIGHT:
Customer "1" --> "0..*" Order
(This reads: "One customer places many orders" - correct!)

Multiplicity cheat sheet:

  • 1 = exactly one
  • 0..1 = zero or one (optional)
  • * or 0..* = zero or more
  • 1..* = one or more
  • n..m = between n and m

5. Forgetting to Show Abstract Classes and Interfaces Clearly

Mistake: Not distinguishing abstract classes or interfaces from concrete classes in the diagram.

Why it matters: Abstract classes can’t be instantiated. If someone tries to implement your design without knowing a class is abstract, they’ll write incorrect code.

How to avoid: Use italics for abstract class names and methods, or add {abstract} stereotype. For interfaces, use <<interface>> stereotype.

Example:

# UNCLEAR:
┌─────────────┐
│   Shape     │  # Looks like a regular class
├─────────────┤
│ + draw()    │
└─────────────┘

# CLEAR:
┌─────────────────┐
│   Shape         │  # Italicized or marked
│   {abstract}    │
├─────────────────┤
│ + draw() {abstract}
└─────────────────┘

# OR for interface:
┌─────────────────┐
│ <<interface>>   │
│   Drawable      │
├─────────────────┤
│ + draw()        │
└─────────────────┘

Interview Tips

1. Start with Nouns and Verbs from Requirements

When asked to “design a system,” extract classes from nouns and methods from verbs in the problem statement.

Example: “Design a parking lot where vehicles can park in spots. The lot has multiple levels.”

  • Nouns → Classes: ParkingLot, Vehicle, ParkingSpot, Level
  • Verbs → Methods: park(), leave(), findAvailableSpot()

Pro tip: Say this out loud: “I’ll start by identifying the key entities…” This shows structured thinking.


2. Draw the Diagram Before Writing Code

Interviewers want to see you design before implementing. Even in a coding interview, sketch a quick class diagram.

What to say: “Let me sketch the class structure first to make sure we agree on the design before I start coding.”

Time allocation: Spend 20-30% of your time on the diagram. It prevents costly rewrites later.


3. Explain Relationship Choices Out Loud

Don’t just draw lines — explain why you chose composition over association, or inheritance over interfaces.

Example dialogue:

  • “I’m using composition here because a Car owns its Engine — the engine doesn’t make sense without the car.”
  • “I’m using inheritance here because both Car and Truck are types of Vehicle, so they share common behavior.”
  • “I’m using an interface for Payment because we want to support multiple payment methods without tight coupling.”

Why this matters: It shows you understand the tradeoffs, not just the syntax.


4. Know How to Extend Your Design

Interviewers often ask: “How would you add feature X?” or “What if requirements change to Y?”

Prepare to discuss:

  • Where would you add a new class?
  • Which relationships would change?
  • Would you need to refactor existing classes?

Example: “If we need to add a Motorcycle class, I’d extend Vehicle and add specific attributes like hasSidecar. The existing Fleet class wouldn’t need changes because it works with the Vehicle abstraction.”


5. Practice Common Design Problems

These problems appear frequently in interviews. Practice drawing class diagrams for:

  1. Parking Lot System: Vehicles, spots, levels, payment
  2. Library Management: Books, members, loans, fines
  3. E-commerce Platform: Products, orders, customers, payments
  4. Hotel Booking System: Rooms, reservations, guests, payments
  5. ATM System: Accounts, transactions, cards, cash dispensers

Practice drill: Set a timer for 10 minutes. Draw a class diagram for one of these. Then implement the core classes in code.


6. Discuss Design Patterns When Relevant

If your class diagram uses a design pattern, mention it. This shows advanced knowledge.

Examples:

  • “I’m using the Strategy pattern here for different payment methods.”
  • “This is a Factory pattern — the VehicleFactory creates different vehicle types.”
  • “I’m applying the Observer pattern so the UI can react to order status changes.”

Warning: Don’t force patterns where they don’t fit. Only mention them if they naturally solve the problem.


7. Handle Ambiguity Confidently

Real-world requirements are vague. Interviewers test how you handle ambiguity.

When unclear, ask:

  • “Should a customer be able to have multiple addresses, or just one?”
  • “Can a product belong to multiple categories?”
  • “Do we need to track order history, or just current orders?”

Then state your assumption: “I’ll assume one address per customer for now, but we can easily extend this to a one-to-many relationship later.”


8. Watch for These Red Flags in Your Design

Interviewers look for these common issues:

  • God classes: One class doing everything (e.g., a System class with 20 methods)
  • Circular dependencies: ClassA depends on ClassB, which depends on ClassA
  • Missing abstractions: Concrete classes everywhere, no interfaces or abstract classes
  • Over-engineering: 10 classes for a simple problem

Self-check question: “Is each class doing one thing well, or is it doing too much?“


9. Be Ready to Code from Your Diagram

After drawing the diagram, the interviewer will likely say: “Now implement the core classes.”

Transition smoothly: “Based on this design, I’ll start with the Vehicle abstract class since Car and Truck depend on it.”

Implementation order:

  1. Base classes / interfaces first
  2. Concrete implementations next
  3. Classes with dependencies last

10. Know the Difference Between Class and Object Diagrams

Interviewers sometimes ask: “Can you show me an object diagram for this scenario?”

  • Class diagram: Shows the structure (classes and relationships)
  • Object diagram: Shows specific instances at runtime

Example: Class diagram shows Customer and Order classes. Object diagram shows “Alice (Customer) placed Order #123 containing 2 items.”

Key Takeaways

  • Class diagrams show static structure: They visualize classes, attributes, methods, and relationships, serving as blueprints before coding.
  • Master the four key relationships: Association (knows-about), Aggregation (has-a, shared), Composition (owns, strong), Inheritance (is-a). Each has different lifecycle and coupling implications.
  • Use visibility modifiers correctly: + public, - private, # protected. This communicates encapsulation decisions to other developers.
  • Multiplicity matters: Always specify how many instances participate in relationships (1, 0.., 1..). Read relationships as sentences to verify correctness.
  • Design before coding: In interviews, sketch the class diagram first. It shows structured thinking and prevents costly rewrites. Explain your relationship choices out loud to demonstrate understanding of design tradeoffs.