Skip to content

Reading Class and Sequence Diagrams

The small subset of UML you need to read any pattern description — the class box, five relationships, multiplicity, and messages over time.

Beginner19 minUpdated 8 August 2026

Every pattern in this module is described with a diagram, and every design interview eventually reaches a whiteboard. You do not need to draw formal UML — almost nobody does any more. You need to read it, and the readable subset is small enough to learn in one sitting.

Two diagram types carry almost all the weight. A class diagram shows structure: what exists and how it is connected. A sequence diagram shows behaviour: what happens, in what order. Most confusion about a design comes from trying to answer a behaviour question with a structure diagram.

The class box

A class is a rectangle in up to three compartments: the name, the data, the operations.

classDiagram
    class PaymentGateway {
        <<interface>>
        +charge(amountInCents, currency) Receipt
        +refund(receiptId) Receipt
    }
    class StripeGateway {
        -apiKey: string
        -timeoutMs: int
        +charge(amountInCents, currency) Receipt
        +refund(receiptId) Receipt
        -signRequest(body) string
    }
    class Checkout {
        -gateway: PaymentGateway
        +placeOrder(cart) Order
    }
    PaymentGateway <|.. StripeGateway
    Checkout --> PaymentGateway

Three things are worth naming in that picture.

The visibility markers. + is public, - is private, # is protected. They are a claim about what other code is allowed to depend on — the public rows are the promise, the private rows are the mechanism.

The <<interface>> stereotype. Angle-quoted text above a name says what kind of thing this is rather than what it is called. You will also meet <<abstract>> and <<enumeration>>.

What is deliberately absent. Checkout names PaymentGateway, not StripeGateway. That single arrow is the entire claim of the design: checkout can be tested and can change providers without editing. Read the arrows first; the compartments are detail.

Five relationships, and the one distinction that matters

NotationNameRead it as
Solid line, hollow triangleInheritance"is a kind of" — subclass extends class
Dashed line, hollow triangleRealisation"promises to provide" — class implements interface
Solid line, open arrowAssociation"knows about" and holds a reference
Solid line, hollow diamondAggregation"has, but does not own" — the part outlives the whole
Solid line, filled diamondComposition"has and owns" — the part dies with the whole
Dashed line, open arrowDependency"uses briefly" — a parameter, a return type, a local
classDiagram
    class Order
    class OrderLine
    class Customer
    class TaxCalculator
    Order *-- OrderLine
    Order o-- Customer
    Order ..> TaxCalculator

The distinction people get wrong is aggregation versus composition, and it is a lifetime question, not an importance question. Delete an order and its lines are meaningless, so lines are composition — filled diamond. Delete an order and the customer carries on existing and holding other orders, so the customer is aggregation — hollow diamond. If you cannot say which, use plain association; a wrong diamond misleads, an absent one merely omits.

Dependency is the weakest and the most overlooked. Order never stores a TaxCalculator; it receives one, uses it and forgets it. That dashed arrow is why the two can be released independently, and it is often the relationship a good design is trying to reach.

Multiplicity

Numbers at the ends of a line say how many. 1, 0..1, 1..*, *, or an exact 2.

classDiagram
    class Playlist
    class Track
    Playlist "1" o-- "0..*" Track

Read it from each end separately: one playlist holds zero or more tracks, and a track belongs to one playlist. Multiplicity is where real design questions hide — 0..* in both directions means you have a join table and a set of ordering questions you have not answered yet.

Sequence diagrams: the same design over time

A class diagram cannot tell you what happens when someone clicks Pay. A sequence diagram can. Participants sit across the top, time runs downwards.

sequenceDiagram
    participant C as Checkout
    participant G as PaymentGateway
    participant R as OrderRepository
    participant E as EventBus
    C->>G: charge(amount, currency)
    activate G
    G-->>C: Receipt
    deactivate G
    C->>R: save(order)
    R-->>C: ok
    C-)E: publish(order.placed)
    Note over E: subscribers run independently

A solid arrowhead is a call and the caller waits. charge blocks checkout until a receipt comes back, which is why a slow gateway is a slow checkout.

A dashed arrow is a return. It is not a new call; it is the answer to the one above it.

An open arrowhead is asynchronous — the caller does not wait. publish returns immediately and the subscribers run on their own. That single difference in arrowhead is the difference between "the warehouse notification failed and the customer saw an error" and "the warehouse notification failed and nobody noticed".

The vertical bar is an activation. It shows how long a participant is busy, and stacked bars are where latency accumulates.

Sequence diagrams also carry alt for branches and loop for repetition — but a diagram that needs six of them is trying to be source code, and the source code is better at it.

Which diagram answers which question

QuestionDiagram
What can change without breaking what?Class
Who owns this data, and what happens when it is deleted?Class, with diamonds and multiplicity
Why is this request slow?Sequence, reading the activation bars
What happens if the third call fails?Sequence, with the failure branch actually drawn
Which of these is the extension point?Class, looking for the interface

What a diagram cannot tell you

It is a claim, not a proof. A diagram is drawn from intention. The code may already disagree, and no diagram tells you it has drifted.

Error paths are invisible unless drawn. Most sequence diagrams show the happy path only, and most production incidents live in what they omitted.

Nothing about performance or concurrency is implied. Two participants side by side may run in parallel, in sequence, or on the same thread. Only the arrows say, and only if you drew them carefully.

Naming is not modelled. A box labelled Manager passes every validity check UML has and tells the reader nothing.

Check your understanding

Common mistakes

Answering a behaviour question with a class diagram. "Why is this slow" and "what happens if it fails" are never visible in structure.

Using diamonds to mean importance. They mean lifetime. If deleting the whole should not delete the part, the diamond is hollow or absent.

Drawing every method and field. A diagram exists to make one point. Show the participants and the relationships that make that point, and leave the rest to the code.

Omitting multiplicity. It is short to write and it is where the real questions are — nullability, ordering, and whether you have a join table.

Drawing only the happy path. The failure branch is the interesting one, and the one that turns into an incident.

Letting the diagram outlive the code. A confidently wrong diagram is worse than none, because people trust it. If it is not maintained, date it or delete it.

Practice

Sketch, in words, the class diagram for a notification system that can send by email, SMS or push; where a user has notification preferences; and where every send attempt is recorded. State every relationship type and every multiplicity, and justify the two you were least sure about.

Write your own answer first, on paper or in a note. Then compare.

Show a hint

Decide lifetime for each relationship before you decide the symbol.

Show a strong answer

The boxes: Notification (what is being sent), Channel as an interface with EmailChannel, SmsChannel and PushChannel realising it, User, NotificationPreference, and SendAttempt.

The relationships:

  • EmailChannel, SmsChannel, PushChannel realise Channel — dashed line, hollow triangle. Each promises the same operation, which is what lets the sender hold Channel and know nothing else.
  • NotificationService depends on Channel — dashed open arrow, not a solid one, if the channel is passed in per send rather than stored. This is the relationship that makes the service testable.
  • User "1" composes "0..*" NotificationPreference — filled diamond. A preference has no meaning without its user and should be deleted with them.
  • Notification "1" aggregates "1" User — hollow diamond. The notification references a recipient who long outlives it.
  • Notification "1" composes "1..*" SendAttempt — filled diamond, at least one attempt because a notification that was never attempted is not yet a notification.

The two I was least sure about, and how I decided:

Notification to User. The temptation is composition, because a notification always has a user. But composition is about lifetime, not obligation: deleting a notification must not delete the user. Hollow diamond, and the "always has one" part is expressed by the multiplicity 1, not by the symbol.

SendAttempt — composition of the notification, or an independent audit record? It depends on a requirement the diagram forces you to confront: if attempts feed a retention-limited audit log that is purged on its own schedule, they are not owned by the notification, and modelling them as composition will produce a delete cascade that silently destroys audit history. I chose composition on the assumption that attempts are part of the notification's own record, and I would flag that assumption explicitly — this is exactly the kind of question drawing the diagram is supposed to surface.

A sequence diagram for "place order" shows: Checkout →(solid)→ Inventory reserve; Inventory --> Checkout ok; Checkout →(open)→ EventBus publish. A reviewer says the inventory call should be asynchronous too, "for performance". Argue both sides and give a verdict.

Write your own answer first, on paper or in a note. Then compare.

Show a hint

Ask what the caller can honestly tell the user once the call no longer blocks.

Show a strong answer

The case for making it asynchronous: reserving inventory is the slowest step, it blocks the user's response, and its failures — a timeout, a slow lock — become checkout failures. Removing it from the critical path improves the response time directly and stops one dependency's availability from capping checkout's.

The case against, which is the stronger one: the arrowhead is not a performance setting, it is a claim about what the response means.

  • Reserving inventory is a precondition for the order being valid. If it no longer blocks, checkout must return "order received" rather than "order confirmed", because it genuinely does not know yet.
  • Failure now has nowhere to go. With a synchronous call, an out-of-stock item is an error the user can act on. With an asynchronous one it arrives after the user has left, and someone must build the compensation — an apology email, a refund, a cancellation — that did not exist before.
  • The rest of the system inherits the change. Anything downstream that assumed a confirmed order had reserved stock is now wrong, and nothing in the diagram flags it.

Verdict: keep it synchronous, and treat the latency as a latency problem. Make the reservation faster, put a timeout and a circuit breaker around it, or narrow what is reserved at checkout time. Those attack the actual complaint without changing what a successful checkout promises.

What would change the verdict: a business decision to accept oversell — common where stock is plentiful and a rare apology is cheaper than a slow checkout. That is a product call, not a diagram call, and if it is made, the response text, the compensation path and the downstream assumptions all have to change with it.

The general rule worth taking away: synchronous versus asynchronous is a question about the meaning of the response, and performance is a consequence of the answer rather than a reason for it.

Key takeaways

  • Class diagrams answer structural questions; sequence diagrams answer behavioural ones. Most confusion is a behaviour question asked of a structure diagram.
  • Read the arrows before the boxes. Which interface a class points at is usually the entire design claim.
  • Diamonds encode lifetime, not importance — filled means the part dies with the whole, hollow means it does not, and no diamond is better than a wrong one.
  • Multiplicity is where the real questions hide: nullability, ordering and join tables.
  • On a sequence diagram the arrowhead is the highest-information mark — it decides whether latency accumulates and whether failure is visible.
  • A diagram is a claim, not a proof. It omits error paths, says nothing about concurrency, and drifts from the code silently.
  • On a whiteboard, use fewer symbols and say the relationship aloud. Only the arrow directions are worth getting exactly right.

Further reading

  • Martin Fowler, UML Distilled, third edition (Addison-Wesley, 2003) — the readable subset, and the argument for using less of it.
  • Grady Booch, James Rumbaugh and Ivar Jacobson, The Unified Modeling Language User Guide, second edition (Addison-Wesley, 2005).
  • Object Management Group, OMG Unified Modeling Language Specification, version 2.5.1 (2017) — the normative source for the notation.
  • Simon Brown, Software Architecture for Developers (Leanpub, 2012) — the C4 model, a lighter alternative for system-level diagrams.
  • Eric Evans, Domain-Driven Design (Addison-Wesley, 2003) — on models as shared language rather than documentation.

Check yourself

3 questions in this lesson. Answer each once to complete it.

Practice this

Next lessonFactory Method
Lessons in this track