What a class diagram is

A class diagram is a UML diagram for showing the static structure of a system.

It answers questions like:

  • what important things exist in the system
  • what data those things hold
  • what behavior they expose
  • how those things relate to each other

It is called a class diagram because the main building block is the class, but in practice class diagrams are often used more broadly to model:

  • domain concepts
  • software objects and services
  • architectural responsibilities at a medium level

Think of a class diagram as a map of structure, not a movie of behavior.

It does not primarily show:

  • time order
  • workflow
  • branching logic
  • runtime message flow

Those belong more naturally to sequence, activity, or state diagrams.


Why class diagrams matter

Class diagrams are one of the most useful UML tools because they help you:

  • understand a problem domain before coding
  • identify the main entities and their responsibilities
  • reason about relationships and ownership
  • communicate design to other people
  • prevent vague or duplicated concepts

If a team cannot clearly name the main concepts in a system and how they relate, the code usually becomes messy later.


What a class diagram is good for

Use class diagrams when you need to answer:

  • what concepts exist in the system?
  • what does each concept know?
  • what can each concept do?
  • which concepts are connected?
  • which concept owns which data?
  • how many of one thing can relate to another?

Class diagrams are especially useful for:

  • domain modeling
  • object-oriented design discussions
  • API and service design at a conceptual level
  • onboarding and documentation

The basic shape of a class

In UML, a class is usually drawn as a rectangle with up to three compartments:

  1. the class name
  2. attributes
  3. operations

Example:

classDiagram
    class Book {
        +isbn: String
        +title: String
        +author: String
        +isAvailable(): Boolean
    }

How to read that

  • Book is the class name
  • isbn, title, and author are attributes
  • isAvailable() is an operation
  • + means public visibility

You do not always need to show all three compartments. A diagram can be useful even with only class names and relationships.


Class names

A class name usually:

  • is singular, not plural
  • is a noun or noun phrase
  • represents one kind of thing

Good examples:

  • Book
  • Member
  • Invoice
  • Payment
  • Reservation

Less useful examples:

  • Books
  • DataManagerStuff
  • SystemThing

Good class names improve the design because they force clearer thinking.


Attributes

Attributes describe the data a class holds.

Example:

classDiagram
    class Member {
        +memberId: String
        +name: String
        +email: String
        +joinedOn: Date
    }

Attribute format

The common format is:

visibility name: Type

Example:

  • +name: String
  • -balance: Decimal
  • #status: OrderStatus

You can omit types if the diagram is conceptual and types would add noise.


Operations

Operations describe what a class can do.

Example:

classDiagram
    class Order {
        +addItem(product: Product, qty: int)
        +calculateTotal(): Decimal
        +submit()
        +cancel()
    }

Operation format

The common format is:

visibility name(parameter: Type): ReturnType

Example:

  • +borrow(book: Book)
  • +isExpired(): Boolean
  • +assign(driver: Driver)

In early domain modeling, you can keep operations minimal. If you list too many methods, the diagram starts turning into code.


Visibility

UML commonly uses these visibility markers:

  • + public
  • - private
  • # protected
  • ~ package

Example:

classDiagram
    class Account {
        -passwordHash: String
        +email: String
        +resetPassword()
    }

For many practical notes, visibility is optional. Use it when it helps understanding.


Types

Types tell you what kind of value an attribute or parameter has.

Examples:

  • String
  • Date
  • Boolean
  • Money
  • OrderStatus

Types are useful when:

  • the domain meaning matters
  • you want the model to be more precise
  • you are showing constraints or design intent

Types are less useful when:

  • you are still brainstorming concepts
  • the diagram becomes cluttered

The most important idea: structure, not execution

A class diagram shows what exists and how it is connected.

It does not show:

  • step-by-step request flow
  • who calls whom first
  • branching over time
  • lifecycle transitions

If you find yourself writing a story of events, you probably need a sequence or activity diagram instead.


Relationships: the heart of class diagrams

Most of the value in a class diagram comes from the relationships.

The main ones you should know are:

  • association
  • aggregation
  • composition
  • inheritance
  • realization
  • dependency

You do not need to memorize every UML symbol before you can use class diagrams well. First understand the meaning.


1. Association

An association means two classes are related.

Example:

classDiagram
    class Customer
    class Order

    Customer --> Order : places

This means:

  • Customer and Order are connected
  • the label places explains the meaning of the connection

In plain language:

  • a customer places orders

Association is the most common relationship.

A more neutral notation

Sometimes people draw it without suggesting direction:

classDiagram
    class Teacher
    class Course

    Teacher -- Course : teaches

This simply says the classes are related.


2. Directed association

A directed association suggests one class knows about or refers to the other.

Example:

classDiagram
    class Invoice
    class Customer

    Invoice --> Customer : billedTo

This often implies:

  • Invoice holds or uses a reference to Customer
  • the navigation is mainly from invoice to customer

Do not over-interpret arrows in high-level conceptual diagrams. Use them only when direction matters.


3. Multiplicity

Multiplicity tells you how many instances can participate in a relationship.

Common multiplicities:

  • 1 exactly one
  • 0..1 zero or one
  • * many
  • 0..* zero or many
  • 1..* one or many
  • 2..5 between two and five

Example:

classDiagram
    class Customer
    class Order

    Customer "1" --> "0..*" Order : places

This means:

  • one Order is placed by one Customer
  • one Customer can place zero or many Orders

Multiplicity is one of the most useful parts of a class diagram because it reveals business rules.


4. Aggregation

Aggregation is a weak whole-part relationship.

Example idea:

  • a Team has Players
  • players can still exist independently of a team

Example:

classDiagram
    class Team
    class Player

    Team o-- Player : has

Meaning:

  • Team groups Players
  • but Player is not completely owned by one team for its existence as a concept

In practice, many teams use plain association unless the distinction is important.


5. Composition

Composition is a strong whole-part relationship.

The part strongly belongs to the whole.

Classic example:

  • an Order contains OrderItems
classDiagram
    class Order
    class OrderItem

    Order *-- OrderItem : contains

Meaning:

  • OrderItem is part of an Order
  • the item usually does not make sense by itself outside the order

Composition often implies stronger lifecycle ownership.

Another good example:

  • House and Room

This is stronger than simple association because the room is modeled as a structural part of the house.


6. Inheritance

Inheritance shows a more general class and more specific subclasses.

Example:

classDiagram
    class User {
        +id: String
        +name: String
    }

    class Member {
        +membershipLevel: String
    }

    class Librarian {
        +employeeId: String
    }

    User <|-- Member
    User <|-- Librarian

Meaning:

  • Member is a kind of User
  • Librarian is a kind of User
  • common structure can live in the parent class

Use inheritance carefully. It is easy to overuse. Prefer it only when there is a genuine “is-a” relationship.


7. Realization

Realization is used when a class implements an interface.

Example:

classDiagram
    class PaymentGateway {
        <<interface>>
        +charge(amount: Money): Receipt
    }

    class StripeGateway
    class PaypalGateway

    PaymentGateway <|.. StripeGateway
    PaymentGateway <|.. PaypalGateway

Meaning:

  • PaymentGateway defines a contract
  • concrete classes implement it

This is more common in software design diagrams than in pure domain modeling.


8. Dependency

A dependency means one class temporarily uses another.

Example idea:

  • ReportGenerator uses InvoiceRepository
classDiagram
    class ReportGenerator
    class InvoiceRepository

    ReportGenerator ..> InvoiceRepository : uses

This is weaker than a structural association. It often means:

  • method-level use
  • temporary collaboration
  • no long-term ownership

Reading a class diagram step by step

When you see a class diagram, read it in this order:

  1. identify the main classes
  2. read relationship labels
  3. check multiplicities
  4. notice whole-part relationships
  5. look for inheritance or interfaces
  6. inspect attributes and operations only after the structure is clear

This order prevents you from getting lost in details too early.


Very small example 1: Student and Course

This is a good beginner example for many-to-many structure.

classDiagram
    class Student {
        +studentId: String
        +name: String
    }

    class Course {
        +courseCode: String
        +title: String
    }

    Student "0..*" --> "0..*" Course : enrolls in

What it teaches

  • one student can enroll in many courses
  • one course can have many students
  • multiplicity can exist on both sides

What it does not show

It does not show:

  • enrollment date
  • grade
  • semester

As soon as those details matter, the relationship should often become its own class.


Very small example 2: Student, Course, and Enrollment

This is the better model when the relationship has its own data.

classDiagram
    class Student {
        +studentId: String
        +name: String
    }

    class Course {
        +courseCode: String
        +title: String
    }

    class Enrollment {
        +enrolledOn: Date
        +grade: String
        +status: EnrollmentStatus
    }

    Student "1" --> "0..*" Enrollment : has
    Course "1" --> "0..*" Enrollment : includes

Important lesson

If a relationship has its own attributes, that is often a signal that it should be modeled as a separate class.

This pattern appears constantly in real systems.

Examples:

  • Enrollment
  • OrderItem
  • Booking
  • Membership
  • Assignment

Very small example 3: Order and OrderItem

This is a classic composition example.

classDiagram
    class Order {
        +orderNumber: String
        +createdOn: Date
        +calculateTotal(): Money
    }

    class OrderItem {
        +quantity: int
        +unitPrice: Money
        +subtotal(): Money
    }

    class Product {
        +sku: String
        +name: String
    }

    Order "1" *-- "1..*" OrderItem : contains
    OrderItem --> Product : refers to

What it teaches

  • an order is made of items
  • each item refers to a product
  • a product is not the same thing as an order item

That distinction matters. A product is catalog information. An order item is a purchased line in a particular order.


Very small example 4: User and Session

This is useful for authentication or web systems.

classDiagram
    class User {
        +userId: String
        +email: String
    }

    class Session {
        +sessionId: String
        +createdAt: DateTime
        +expiresAt: DateTime
        +isExpired(): Boolean
    }

    User "1" --> "0..*" Session : owns

What it teaches

  • one user can have multiple sessions
  • sessions are separate entities, not just fields inside user
  • the diagram helps separate account identity from login activity

Real example 1: Online library system

This continues the same domain used in the first note.

classDiagram
    class Member {
        +memberId: String
        +name: String
        +borrow(copy: BookCopy)
        +return(copy: BookCopy)
    }

    class Book {
        +isbn: String
        +title: String
        +author: String
    }

    class BookCopy {
        +barcode: String
        +status: CopyStatus
        +markLost()
    }

    class Loan {
        +loanDate: Date
        +dueDate: Date
        +returnedOn: Date
        +close()
    }

    class Reservation {
        +reservedOn: Date
        +status: ReservationStatus
        +cancel()
    }

    Book "1" *-- "1..*" BookCopy : has copies
    Member "1" --> "0..*" Loan : creates
    BookCopy "1" --> "0..*" Loan : appears in
    Member "1" --> "0..*" Reservation : makes
    Book "1" --> "0..*" Reservation : reserved for

Why this model is better than a naive one

Many beginners connect Member directly to Book.

That is too vague because:

  • libraries often own multiple physical copies of one book
  • loans usually happen on copies, not on the abstract title

This is a key modeling lesson:

  • separate the conceptual item from the physical or transactional item

Similar pairs in other systems:

  • Product vs InventoryUnit
  • Event vs Ticket
  • VehicleModel vs Vehicle

Real example 2: E-commerce order model

This is one of the most reusable examples across real software.

classDiagram
    class Customer {
        +customerId: String
        +name: String
        +email: String
    }

    class Address {
        +line1: String
        +city: String
        +postalCode: String
        +country: String
    }

    class Order {
        +orderId: String
        +placedOn: DateTime
        +status: OrderStatus
        +calculateTotal(): Money
    }

    class OrderItem {
        +quantity: int
        +unitPrice: Money
    }

    class Product {
        +sku: String
        +name: String
        +listPrice: Money
    }

    class Payment {
        +paymentId: String
        +amount: Money
        +status: PaymentStatus
        +authorize()
        +refund()
    }

    Customer "1" --> "0..*" Order : places
    Customer "1" --> "0..*" Address : has
    Order "1" *-- "1..*" OrderItem : contains
    OrderItem --> Product : for
    Order "1" --> "0..*" Payment : paid by
    Order --> Address : ships to

What this teaches

  • OrderItem is not optional noise; it is structurally important
  • Payment is separate from Order
  • Address is often its own concept
  • class diagrams help avoid collapsing too many responsibilities into one giant Order class

Real example 3: Hotel booking system

This is good for reservations, time-based relationships, and lifecycle-heavy domains.

classDiagram
    class Guest {
        +guestId: String
        +name: String
        +phone: String
    }

    class Room {
        +roomNumber: String
        +type: RoomType
        +nightlyRate: Money
    }

    class Reservation {
        +reservationId: String
        +checkInDate: Date
        +checkOutDate: Date
        +status: ReservationStatus
        +confirm()
        +cancel()
    }

    class Payment {
        +paymentId: String
        +amount: Money
        +paidOn: DateTime
    }

    Guest "1" --> "0..*" Reservation : makes
    Room "1" --> "0..*" Reservation : assigned to
    Reservation "1" --> "0..*" Payment : settled by

Modeling lesson

Notice that:

  • a reservation connects a guest and a room
  • the reservation holds the dates and status

This is another case where the relationship itself is a first-class concept.


Real example 4: School management system

This example combines inheritance, relationship classes, and composition-like ownership.

classDiagram
    class Person {
        +id: String
        +name: String
        +email: String
    }

    class Student {
        +rollNumber: String
    }

    class Teacher {
        +employeeNumber: String
    }

    class Course {
        +code: String
        +title: String
    }

    class Enrollment {
        +enrolledOn: Date
        +grade: String
    }

    class Assignment {
        +title: String
        +dueDate: Date
    }

    Person <|-- Student
    Person <|-- Teacher
    Teacher "1" --> "0..*" Course : teaches
    Student "1" --> "0..*" Enrollment : has
    Course "1" --> "0..*" Enrollment : includes
    Course "1" *-- "0..*" Assignment : contains

What this teaches

  • shared identity can sit in a parent class
  • enrollment holds relationship data
  • assignments are structural parts of a course in this model

Real example 5: Ride-sharing system

This example helps with roles, transactional entities, and temporary collaborations.

classDiagram
    class Rider {
        +riderId: String
        +name: String
    }

    class Driver {
        +driverId: String
        +name: String
        +rating: Decimal
    }

    class Vehicle {
        +plateNumber: String
        +model: String
    }

    class Trip {
        +tripId: String
        +requestedAt: DateTime
        +startedAt: DateTime
        +endedAt: DateTime
        +fare: Money
    }

    class Payment {
        +paymentId: String
        +amount: Money
        +status: PaymentStatus
    }

    Driver "1" --> "1" Vehicle : uses
    Rider "1" --> "0..*" Trip : requests
    Driver "1" --> "0..*" Trip : serves
    Trip "1" --> "1" Payment : settled by

What this teaches

  • trips are central business events
  • rider and driver are connected through trips, not directly
  • payment belongs to the trip context

This pattern is common: many systems are best modeled around a transactional record in the middle.


Real example 6: Issue tracking system

This is useful for software engineering domains.

classDiagram
    class User {
        +userId: String
        +name: String
    }

    class Project {
        +projectKey: String
        +name: String
    }

    class Issue {
        +issueId: String
        +title: String
        +description: String
        +status: IssueStatus
        +priority: Priority
    }

    class Comment {
        +commentId: String
        +body: String
        +createdAt: DateTime
    }

    class Label {
        +name: String
    }

    Project "1" --> "0..*" Issue : contains
    User "0..1" --> "0..*" Issue : assigned to
    User "1" --> "0..*" Comment : writes
    Issue "1" *-- "0..*" Comment : has
    Issue "0..*" --> "0..*" Label : tagged with

What this teaches

  • comments belong structurally to issues
  • assignment can be optional
  • labels often create many-to-many tagging relationships

How to build a class diagram for any system

When modeling a new system, use this process.

Step 1: list the important nouns

Start from requirements or user stories and extract nouns.

Example from an e-commerce story:

  • customer
  • cart
  • order
  • product
  • payment
  • address

Not every noun becomes a class, but this is the right starting point.

Step 2: remove vague or duplicate concepts

Ask:

  • are Account and User actually different?
  • is OrderLine the same as OrderItem?
  • is Catalog a concept or just a view over products?

Clarity here matters more than drawing speed.

Step 3: identify relationships

Ask:

  • which things are connected?
  • which things contain other things?
  • which things are just references?
  • where does ownership matter?

Step 4: add multiplicity

Ask:

  • one or many?
  • optional or required?
  • can this exist without that?

This is where business rules become concrete.

Step 5: promote important relationships into classes

If a relationship has data of its own, make it a class.

Common signals:

  • dates
  • status
  • quantity
  • role
  • price
  • grade

Step 6: add only important attributes and operations

Do not dump everything from the code or database.

Include:

  • identity fields
  • domain-relevant attributes
  • a few meaningful operations if needed

Skip:

  • trivial getters/setters
  • implementation detail fields
  • framework boilerplate

Step 7: split diagrams if needed

If the diagram becomes crowded:

  • make one diagram for billing
  • one for ordering
  • one for inventory

One giant class diagram is usually a bad diagram.


How class diagrams differ from database schemas

Beginners often confuse class diagrams with ER diagrams or table schemas.

They overlap, but they are not the same.

Class diagrams focus on:

  • concepts
  • behavior
  • responsibilities
  • object relationships

Database schemas focus on:

  • persistence structure
  • normalization
  • keys
  • storage constraints

Example:

  • Order.calculateTotal() belongs naturally in a class diagram
  • index strategy does not

Do not turn your class diagram into a database dump unless the goal is specifically persistence modeling.


How class diagrams differ from code

A class diagram is not a printout of source code.

If you mirror every class, every field, and every method from code, the diagram often becomes useless.

A useful class diagram is selective.

It should emphasize:

  • important concepts
  • meaningful relationships
  • the design choices people need to understand

Common modeling patterns you will reuse

1. Entity and relationship class

Pattern:

  • two main concepts
  • a middle class stores relationship details

Examples:

  • Student - Enrollment - Course
  • Order - OrderItem - Product
  • Guest - Reservation - Room

2. Abstract parent with specialized children

Pattern:

  • shared identity or behavior in a parent
  • specialized roles in children

Examples:

  • User -> Member, Librarian
  • Person -> Student, Teacher
  • PaymentMethod -> CardPayment, UpiPayment

3. Whole and parts

Pattern:

  • one thing structurally contains others

Examples:

  • Order -> OrderItem
  • Course -> Assignment
  • Invoice -> InvoiceLine

4. Central transaction model

Pattern:

  • major actors are linked through an event or record

Examples:

  • Rider - Trip - Driver
  • Buyer - Order - Seller
  • Patient - Appointment - Doctor

These patterns cover a large share of real systems.


Mermaid syntax for practical class diagrams

Since you are keeping notes in Markdown, Mermaid is a good default.

Simple class

classDiagram
    class Book

Class with attributes and operations

classDiagram
    class Book {
        +isbn: String
        +title: String
        +isAvailable(): Boolean
    }

Association

classDiagram
    Customer --> Order : places

Multiplicity

classDiagram
    Customer "1" --> "0..*" Order : places

Composition

classDiagram
    Order *-- OrderItem : contains

Aggregation

classDiagram
    Team o-- Player : has

Inheritance

classDiagram
    User <|-- Admin

Interface realization

classDiagram
    Gateway <|.. StripeGateway

Dependency

classDiagram
    ReportService ..> Repository : uses

Practical rules for good class diagrams

Use these rules consistently:

1. Model concepts, not raw storage

Ask:

  • what thing matters in the domain?

Do not start from tables or JSON fields.

2. Keep one abstraction level per diagram

Do not mix:

  • business actors
  • database rows
  • REST endpoints
  • UI widgets

in the same class diagram.

3. Use multiplicity carefully

Multiplicity is not decoration. It encodes rules.

If you are unsure, ask:

  • is this optional?
  • can there be many?
  • can this exist without the other?

4. Use composition only when ownership is strong

Not every “has-a” relationship is composition.

Order *-- OrderItem makes sense.

Company *-- Employee is usually more debatable, because employees still exist independently as people and often as domain entities.

5. Do not overuse inheritance

Prefer inheritance only for strong “is-a” relationships.

Bad inheritance usually appears when people try to force code reuse instead of modeling truth.

6. Keep diagrams small enough to think with

A diagram should reduce confusion, not become a wall of boxes.


Common beginner mistakes

1. Making every noun a class

Some nouns are just attributes or labels, not full classes.

Example:

  • Address may deserve a class
  • Color might just be an attribute, depending on context

2. Forgetting multiplicity

Without multiplicity, important rules stay hidden.

3. Confusing product with purchased item

Examples of important distinctions:

  • Product vs OrderItem
  • Book vs BookCopy
  • Course vs Enrollment

4. Treating relationships with data as plain lines

If the relationship has dates, status, quantity, or price, it often deserves its own class.

5. Putting workflow into a class diagram

If you are drawing steps, decisions, or message order, you are probably using the wrong diagram type.

6. Showing too much code detail

A class diagram full of getters, setters, helpers, and framework methods usually hides the real design.


When to use class diagrams

Use class diagrams when:

  • exploring a new domain
  • designing a feature before implementation
  • documenting a subsystem
  • explaining object relationships to a team
  • clarifying ambiguous concepts

Do not force a class diagram when:

  • the main problem is process flow
  • the main problem is runtime interaction order
  • a short paragraph would explain the idea better

What you should know to be competent with class diagrams

You are already doing useful class-diagram work if you can:

  • identify the main domain concepts
  • distinguish attributes from separate classes
  • model associations and multiplicities clearly
  • recognize when a relationship should become its own class
  • use composition and inheritance carefully
  • keep diagrams focused and readable
  • choose examples that reflect real business rules

That is the practical skill that matters.


A final modeling checklist

Before you finish a class diagram, ask:

  1. Are the main concepts named clearly?
  2. Does each class represent one coherent thing?
  3. Are the relationships labeled meaningfully?
  4. Are multiplicities present where they matter?
  5. Did I separate abstract concepts from physical or transactional ones?
  6. Did I turn relationship-with-data cases into classes?
  7. Is the diagram focused on one viewpoint?
  8. Would another engineer understand the model without a spoken explanation?

If the answer to most of those is yes, the diagram is probably useful.


Bottom line

Class diagrams are the most practical UML tool for modeling system structure.

They help you answer:

  • what exists
  • what those things know
  • what they do
  • how they relate

If you learn to model:

  • classes
  • attributes
  • operations
  • associations
  • multiplicities
  • composition
  • inheritance

and if you practice on systems like libraries, e-commerce, booking, school, and issue tracking, you will be able to create class diagrams for most real software domains with confidence.