UML Class Diagram Example: Modeling an Online Course Platform

A worked UML class diagram for an online course platform: classes, visibility, multiplicity, and when to use association, composition, inheritance or an interface.

· Founder & developer

Published

UML Class Diagram Example: Modeling an Online Course Platform

Most UML diagram types live in textbooks. The class diagram is the one engineers still draw: a picture of the things a system is about, what each of them holds, what it can do, and how they relate. Drawn well, it settles arguments about the domain model before anyone writes a migration or an API.

This post builds a UML class diagram for a small online course platform, one decision at a time, and explains the notation as it comes up: visibility, multiplicity, association, composition, inheritance and interfaces. It ends with the mistakes that make class diagrams useless and a short look at how the diagram maps to code.

When a class diagram is the right picture

The UML 2.5.1 specification splits its diagrams into structure diagrams and behavior diagrams. A class diagram is a structure diagram: it shows types, their attributes and operations, and the relationships between them, with no notion of time.

Question you need answered Diagram
Which concepts exist, what they know, how they relate Class diagram
How the data is stored: tables, keys, foreign keys ER diagram
What happens in what order between participants Sequence diagram
Which states one object moves through State machine diagram

A class diagram earns its place for a domain model, for the public types of a library, or for a design you are about to build. It does not earn its place as a dump of every class in a repository: an IDE shows that better, and the picture is out of date after the next commit.

The notation in two tables

A class is a box with up to three compartments: the name, the attributes and the operations. Any compartment can be left out.

Element Notation Meaning
Abstract class Name in italics, or {abstract} Cannot be instantiated; subclasses complete it
Interface «interface» above the name A contract of operations
Visibility + public, - private, # protected, ~ package Who may use the feature
Attribute name: Type [multiplicity] = default Data the object holds
Operation name(param: Type): ReturnType Behavior the object offers
Static feature Underlined Belongs to the class, not to an instance

Relationships are lines between classes, and the ends carry most of the meaning:

Relationship Line Read it as
Association Solid line; an open arrowhead marks a navigable end "is related to"
Aggregation Solid line, hollow diamond at the whole "groups"; the parts live on their own
Composition Solid line, filled diamond at the whole "is made of"; the parts go with the whole
Generalization Solid line, hollow triangle at the parent "is a kind of"
Realization Dashed line, hollow triangle at the interface "implements"
Dependency Dashed line, open arrowhead at the target "uses"; a change to the target can break the source

Composition is the strict one. The specification says a part object belongs to at most one composite at a time, and when the composite is deleted, its parts are deleted with it. Shared aggregation, the hollow diamond, is the vague one: the specification itself says its precise semantics vary by application area and modeler. Use it only if your team agrees what it means; a plain association says the same thing with less debate.

Multiplicity

Multiplicity says how many objects can sit at one end of an association: 1 for exactly one, 0..1 for optional, * (the same as 0..*) for any number, 1..* for at least one, and ranges such as 2..5. Write it at the end next to the class it counts. Read Course 1 — 1..* Module as "a course has one or more modules, and each module belongs to exactly one course."

Missing multiplicities are the most common reason a class diagram starts arguments instead of ending them. Every association in the example below has both ends filled in.

The example: an online course platform

Start from plain sentences, not boxes:

  1. Instructors publish courses. A course has one or more instructors.
  2. A course is made of ordered modules, and a module of ordered lessons.
  3. A lesson is a video, a quiz or an assignment.
  4. Quizzes and assignments are graded; videos are not.
  5. Students enroll in courses. An enrollment tracks progress and holds the student's submissions.
  6. A student who completes a course gets one certificate for that enrollment.
  7. Courses are grouped into categories, and a course can be in several.

Users: one class, two roles

The first instinct is Student and Instructor as subclasses of User. That breaks the first time an instructor takes someone else's course: in most programming languages an object has one class for its whole life, so it cannot be both, or switch later. Model roles as associations instead: a User teaches courses, and a User holds enrollments. Keep inheritance for things that are a kind of something for their whole life, such as a quiz being a kind of lesson.

Composition where lifecycles are tied

A module means nothing outside its course, and a lesson means nothing outside its module. Deleting a course deletes both. That is composition: a filled diamond at Course and at Module, with 1 at the whole. Categories are different. Deleting the "Data engineering" category must not delete its courses, so Category to Course is aggregation with * on both ends.

Inheritance plus an interface

Lesson is abstract; VideoLesson, Quiz and Assignment inherit its title and position. Grading cuts across that tree: quizzes and assignments are graded, videos are not, and a peer review added next year might be graded without being a lesson at all. That is an interface, Gradable, realized by Quiz and Assignment. GradingService depends on Gradable, not on the concrete classes, and the dashed dependency arrow says exactly that.

An association with data of its own

Enrollment sits between User and Course. UML has a dedicated notation for this, the association class, attached to the line by a dashed line. Most teams draw it as an ordinary class with two associations instead, because it has its own identity, data and behavior: the enrollment date, progress, submissions and at most one certificate.

Course is composed of Modules, which are composed of Lessons. VideoLesson, Quiz and Assignment inherit from the abstract Lesson; Quiz and Assignment realize the Gradable interface. Users teach courses and enroll through Enrollment, which holds Submissions and an optional Certificate.

32 components · 16 connections

Open in Archboard
Diagram contents
  • Category
  • Course
  • User
  • Module
  • Enrollment
  • Certificate
  • Lesson
  • Submission
  • VideoLesson
  • Quiz
  • Assignment
  • Gradable
  • GradingService
  • groups
  • teaches
  • enrolls
  • for
  • earns
  • holds
  • answers
  • «use»
A UML class diagram for an online course platform: courses composed of modules and lessons, three lesson types under one abstract class, gradable work behind an interface, and enrollments that link users to courses.

How to read it:

  • Top left, the catalog. Course is composed of Module, which is composed of Lesson, with filled diamonds at the whole. Category groups courses with a hollow diamond.
  • Bottom left, lesson types. Lesson is abstract. VideoLesson, Quiz and Assignment point to it with hollow triangles.
  • Right, learning. A User teaches courses, with 1..* instructors per course, and has any number of enrollments, each for exactly one course. An Enrollment holds submissions and earns at most one Certificate.
  • Bottom right, grading. Quiz and Assignment realize Gradable with dashed lines and hollow triangles. Each Submission answers one gradable item, and GradingService depends on the interface only.

Common mistakes

  • Every class in the codebase. Ten to fifteen classes per diagram is a good limit. Split a larger model by area, or show the rest as names without compartments.
  • Getters and setters as operations. List operations that carry domain meaning, such as publish() or enroll(), not accessors.
  • Tables drawn as classes. Attributes like courseId belong in the ER diagram. In a class diagram, the association line is the reference.
  • Inheritance for roles. If an object can gain or lose the role, it is an association, not a subclass.
  • Diamonds without meaning. If nobody can say what deleting the whole does to the parts, use a plain association.
  • Implementation detail at domain level. Leave out framework annotations, DTOs and repositories unless the diagram is about them.

From the diagram to code

The diagram maps to code directly. In TypeScript:

interface Gradable {
  maxScore(): number;
  autoGrade(submission: Submission): Score | null;
}

abstract class Lesson {
  constructor(public title: string, public position: number) {}
}

class Quiz extends Lesson implements Gradable {
  questions: Question[] = [];
  maxScore(): number {
    return this.questions.length;
  }
  autoGrade(submission: Submission): Score | null {
    return submission.scoreAgainst(this.questions);
  }
}

class Course {
  private readonly modules: Module[] = []; // composition: owned and removed with the course
  categories: Category[] = []; // aggregation: categories exist on their own

  addModule(title: string): Module {
    const created = new Module(title, this.modules.length);
    this.modules.push(created);
    return created;
  }
}

Composition becomes ownership: the course creates its modules, and nothing keeps one alive after the course is gone. Aggregation becomes a reference to an object with its own lifecycle. When the code and the diagram disagree, update the diagram in the same pull request; that is far easier when the diagram is a link rather than a screenshot, as described in architecture diagrams that don't rot.

Build it in Archboard

  1. Sign in with Google or GitHub and create a diagram.
  2. From the UML pack, add a Class for each concept and write attributes and operations one per line. Set the stereotype of Lesson to abstract.
  3. Add an Interface for Gradable with its two operations.
  4. Connect the classes. Set the arrow end at the whole to Filled diamond for composition and Diamond for aggregation, and to Triangle at the parent for inheritance. Draw realization as a dashed line with a Triangle at the interface, and dependency as a dashed line with an Open arrow.
  5. Put multiplicities next to each end as text, and name associations such as "teaches" in the edge label.
  6. Add a UML Note for decisions, such as why roles are associations.

The example uses 13 diagram nodes, within the Free plan. With an AI agent connected over MCP, you can describe the requirements and let it draft the classes, then fix the multiplicities yourself.

FAQ

What is the difference between a class diagram and an object diagram? A class diagram shows types and the rules between them. An object diagram shows specific instances at one moment, with their values and links, and is useful for checking a class diagram against a real example.

What is the difference between aggregation and composition? With composition, a part belongs to one whole and is deleted with it. With aggregation, the parts can exist on their own and be shared; its exact meaning is left to the modeler, so many teams use a plain association instead.

Should a class diagram match the code exactly? A domain model should match the concepts and relationships, not every field and method. Keep it at the level where a product manager and an engineer can both read it.

How is a class diagram different from a C4 component diagram? A C4 component diagram shows deployable building blocks and their responsibilities. A class diagram goes one level deeper, to the types inside them; see the C4 model diagram example.

Can I draw a class diagram from code automatically? Many IDEs can, but the result shows every class and is hard to read. Draw the domain model by hand or with an AI agent, and keep it to the classes that matter.

Start free — one seat, no credit card.

Related posts

← Back to blog

Open a canvas in a minute

Sign in with Google or GitHub. Free for a single seat. Upgrade when the team joins.