UML Sequence Diagrams
Unlocking System Behavior with UML Sequence Diagrams
Introduction: The “Who, What, and When” of Systems
Imagine walking into a coffee shop. You place an order with the barista, the barista sends the ticket to the kitchen, the kitchen makes the coffee, and finally, the barista hands it to you. This entire process is a sequence of interactions happening over time.
In software engineering, we need a way to visualize these step-by-step interactions between different parts of a system. This is exactly what Unified Modeling Language (UML) Sequence Diagrams do. They show us who is talking to whom, what they are saying, and in what order.
Learning Objectives
By the end of this chapter, you will be able to:
- Identify the core components of a sequence diagram: Lifelines and Messages.
- Differentiate between synchronous, asynchronous, and return messages.
- Model conditional logic using ALT and OPT fragments.
- Model repetitive behavior using LOOP fragments.
Part 1: The Basics – Lifelines and Messages
To manage your cognitive load, we will start with just the two most fundamental building blocks: the entities communicating, and the communications themselves.
1. Lifelines (The “Who”)
A lifeline represents an individual participant in the interaction. It is drawn as a box at the top (with the participant’s name) and a dashed vertical line extending downwards. Time flows from top to bottom along this dashed line.
2. Messages (The “What”)
Messages are the communications between lifelines. They are drawn as horizontal arrows. UML 2 distinguishes three main arrow styles (sources: Fowler, UML Distilled, ch. 4; Rumbaugh, Jacobson & Booch, The Unified Modeling Language Reference Manual):
- Synchronous Message — solid line with filled (triangular) arrowhead. The sender blocks until the receiver responds, like calling a method and waiting for it to return.
- Asynchronous Message — solid line with open (stick) arrowhead. The sender fires the message and continues immediately, like putting an event on a queue or sending an HTTP POST without awaiting the response.
- Return Message — dashed line with open arrowhead. Represents control (and often a value) returning to the original caller. Return arrows are optional in UML 2: include them when the returned value is important, omit them when a synchronous call obviously returns.
⚠ Common mistake: Students often confuse the filled vs. open arrowhead, treating both as synchronous. The rule: filled = blocks, open = fires-and-forgets. Remember it as “filled is full commitment; open lets go.”
Visualizing the Basics: A Simple ATM Login
Let’s look at the sequence of a user inserting a card into an ATM.
Notice the flow of time: Message 1 happens first, then 2, 3, and 4. The vertical dimension is strictly used to represent the passage of time.
Stop and Think (Retrieval Practice): If the ATM sent an alert to your phone about a login attempt but didn’t wait for you to reply before proceeding, what type of message arrow would represent that alert? (Think about your answer before reading on).
Reveal Answer
An asynchronous message, represented by an open/stick arrowhead, because the ATM does not wait for a response.Part 1.5: Activation Bars and Object Naming
Now that you understand the basic elements, let’s add two important details that appear in real-world sequence diagrams.
Activation Bars (Execution Specifications)
An activation bar (also called an execution specification) is a thin rectangle drawn on a lifeline. It represents the period during which a participant is actively performing an action or behavior—for example, executing a method. Activation bars can be nested across software lifelines and within a single lifeline (e.g., when an object calls one of its own methods). Human actors are usually shown as initiators or recipients, not as executing software behavior, so they normally do not need activation bars.
The blue bars show when each object is actively processing. Notice how the Station is active from when it receives requestStop() until it sends the confirmation, and how the Train has separate execution bars for addStop(), openDoors(), and closeDoors().
Object Naming Convention
Lifelines in sequence diagrams represent specific object instances, not classes. The standard naming convention is:
objectName : ClassName
- If the specific object name matters:
- If only the class matters: (anonymous instance)
- Multiple instances of the same class get distinct names:
This is different from class diagrams, which show classes in general. Sequence diagrams show one particular scenario of interactions between concrete instances.
Consistency with Class Diagrams
When you draw both a class diagram and a sequence diagram for the same system, they must be consistent:
- Every message arrow in the sequence diagram must correspond to a method defined in the receiving object’s class (or a superclass).
- The method names, parameter types, and return types must match between the two diagrams.
Part 2: Adding Logic – Combined Fragments
Real-world systems rarely follow a single, straight path. Things go wrong, conditions change, and actions repeat. UML uses Combined Fragments to enclose portions of the sequence diagram and apply logic to them.
Fragments are drawn as large boxes surrounding the relevant messages, with a tag in the top-left corner declaring the type of logic, such as , , , or .
Common fragment syntax in sequence diagrams:
- Optional behavior:
- Alternatives with guarded branches:
- Repetition:
- Parallel branches:
- Early exit:
- Critical region:
- Interaction reference:
1. The OPT Fragment (Optional Behavior)
The opt fragment is equivalent to an if statement without an else. The messages inside the box only occur if a specific condition (called a guard) is true.
Scenario: A customer is buying an item. If they have a loyalty account, they receive a discount.
Notice the [hasLoyaltyAccount == true] text. This is the guard condition. If it evaluates to false, the sequence skips the entire box.
2. The ALT Fragment (Alternative Behaviors)
The alt fragment is equivalent to an if-else or switch statement. The box is divided by a dashed horizontal line. The sequence will execute only one of the divided sections based on which guard condition is true.
Scenario: Verifying a user’s password.
3. The LOOP Fragment (Repetitive Behavior)
The loop fragment represents a for or while loop. The messages inside the box are repeated as long as the guard condition remains true, or for a specified number of times.
Scenario: Pinging a server until it wakes up (maximum 3 times).
Part 3: Putting It All Together (Interleaved Practice)
To truly understand how these elements work, we must view them interacting in a complex system. Combining different concepts requires you to interleave your knowledge, which strengthens your mental model.
The Scenario: A Smart Home Alarm System
- The user arms the system.
- The system checks all windows.
- It loops through every window.
- If a window is open (ALT), it warns the user. Else, it locks it.
- Optionally (OPT), if the user has SMS alerts on, it texts them.
Part 4: Combined Fragment Reference
The three fragments above (opt, alt, loop) are the most common, but UML defines additional fragment operators:
| Fragment | Meaning | Code Equivalent |
|---|---|---|
| ALT | Alternative branches (mutual exclusion) | if-else / switch |
| OPT | Optional execution if guard is true | if (no else) |
| LOOP | Repeat while guard is true | while / for loop |
| PAR | Parallel execution of fragments | Concurrent threads |
| CRITICAL | Critical region (only one thread at a time) | synchronized block |
| BREAK | Early exit from the enclosing interaction | break / early return |
| REF | Reference to another sequence diagram by name | Function / subroutine call |
When to use
ref: When a shared interaction (e.g., login, authentication, checkout) appears in many sequence diagrams, draw it once as its own diagram and reference it from others with arefframe. This is the sequence-diagram equivalent of factoring out a function.
Part 5: From Code to Diagram
Translating between code and sequence diagrams is a critical skill. Let’s work through a progression of examples.
Example 1: Simple Method Calls
class Register {
public void method(Sale sale, int cashTendered) {
sale.makePayment(cashTendered);
}
}
class Sale {
public void makePayment(int amount) {
Payment payment = new Payment(amount);
payment.authorize();
}
}
class Payment {
Payment(int amount) { }
void authorize() { }
}
class Payment {
public:
explicit Payment(int amount) { }
void authorize() { }
};
class Sale {
public:
void makePayment(int amount) {
Payment payment(amount);
payment.authorize();
}
};
class Register {
public:
void method(Sale& sale, int cashTendered) {
sale.makePayment(cashTendered);
}
};
class Payment:
def __init__(self, amount: int) -> None:
pass
def authorize(self) -> None:
pass
class Sale:
def make_payment(self, amount: int) -> None:
payment = Payment(amount)
payment.authorize()
class Register:
def method(self, sale: Sale, cash_tendered: int) -> None:
sale.make_payment(cash_tendered)
class Payment {
constructor(amount: number) { }
authorize(): void { }
}
class Sale {
makePayment(amount: number): void {
const payment = new Payment(amount);
payment.authorize();
}
}
class Register {
method(sale: Sale, cashTendered: number): void {
sale.makePayment(cashTendered);
}
}
Notice how the Payment constructor call becomes a create message in the sequence diagram. The Payment object appears at the point in the timeline when it is created.
Example 2: Loops in Code and Diagrams
import java.util.List;
class Item {
int getID() { return 0; }
}
class SaleLine {
final String description;
final int total;
SaleLine(String description, int total) {
this.description = description;
this.total = total;
}
}
class B {
void makeNewSale() { }
SaleLine enterItem(int itemId, int quantity) {
return new SaleLine("", 0);
}
void endSale() { }
}
class A {
private final List<Item> items;
private int total;
private String description = "";
A(List<Item> items) {
this.items = items;
}
public void noName(B b, int quantity) {
b.makeNewSale();
for (Item item : getItems()) {
SaleLine line = b.enterItem(item.getID(), quantity);
total = total + line.total;
description = line.description;
}
b.endSale();
}
private List<Item> getItems() {
return items;
}
}
#include <string>
#include <vector>
class Item {
public:
int getID() const { return 0; }
};
struct SaleLine {
std::string description;
int total;
};
class B {
public:
void makeNewSale() { }
SaleLine enterItem(int itemId, int quantity) {
return {"", 0};
}
void endSale() { }
};
class A {
public:
explicit A(std::vector<Item> items) : items(items) { }
void noName(B& b, int quantity) {
b.makeNewSale();
for (const Item& item : getItems()) {
SaleLine line = b.enterItem(item.getID(), quantity);
total = total + line.total;
description = line.description;
}
b.endSale();
}
private:
const std::vector<Item>& getItems() const {
return items;
}
std::vector<Item> items;
int total = 0;
std::string description;
};
from dataclasses import dataclass
class Item:
def get_id(self) -> int:
return 0
@dataclass
class SaleLine:
description: str
total: int
class B:
def make_new_sale(self) -> None:
pass
def enter_item(self, item_id: int, quantity: int) -> SaleLine:
return SaleLine(description="", total=0)
def end_sale(self) -> None:
pass
class A:
def __init__(self, items: list[Item]) -> None:
self._items = items
self._total = 0
self._description = ""
def no_name(self, b: B, quantity: int) -> None:
b.make_new_sale()
for item in self._get_items():
line = b.enter_item(item.get_id(), quantity)
self._total = self._total + line.total
self._description = line.description
b.end_sale()
def _get_items(self) -> list[Item]:
return self._items
class Item {
getID(): number {
return 0;
}
}
type SaleLine = {
description: string;
total: number;
};
class B {
makeNewSale(): void { }
enterItem(itemId: number, quantity: number): SaleLine {
return { description: "", total: 0 };
}
endSale(): void { }
}
class A {
private total = 0;
private description = "";
constructor(private readonly items: Item[]) { }
noName(b: B, quantity: number): void {
b.makeNewSale();
for (const item of this.getItems()) {
const line = b.enterItem(item.getID(), quantity);
this.total = this.total + line.total;
this.description = line.description;
}
b.endSale();
}
private getItems(): Item[] {
return this.items;
}
}
The for loop in code maps directly to a loop fragment. The guard condition [more items] is a Boolean expression that describes when the loop continues.
Example 3: Alt Fragment to Code
Given this sequence diagram:
Equivalent code in four languages:
class A {
private final B b;
private final C c;
A(B b, C c) {
this.b = b;
this.c = c;
}
public void doX(int x) {
if (x < 10) {
b.calculate();
} else {
c.calculate();
}
}
}
class B {
void calculate() { }
}
class C {
void calculate() { }
}
class B {
public:
void calculate() { }
};
class C {
public:
void calculate() { }
};
class A {
public:
A(B& b, C& c) : b(b), c(c) { }
void doX(int x) {
if (x < 10) {
b.calculate();
} else {
c.calculate();
}
}
private:
B& b;
C& c;
};
class B:
def calculate(self) -> None:
pass
class C:
def calculate(self) -> None:
pass
class A:
def __init__(self, b: B, c: C) -> None:
self._b = b
self._c = c
def do_x(self, x: int) -> None:
if x < 10:
self._b.calculate()
else:
self._c.calculate()
class B {
calculate(): void { }
}
class C {
calculate(): void { }
}
class A {
constructor(
private readonly b: B,
private readonly c: C,
) { }
doX(x: number): void {
if (x < 10) {
this.b.calculate();
} else {
this.c.calculate();
}
}
}
Concept Check (Generation): Try translating this code into a sequence diagram before checking the answer:
public class OrderProcessor { public void process(Order order, Inventory inv) { if (inv.checkStock(order.getItemId())) { inv.reserve(order.getItemId()); order.confirm(); } else { order.reject("Out of stock"); } } }Reveal Answer
Real-World Examples
These examples show sequence diagrams for real systems. For each diagram, trace through the arrows top-to-bottom and narrate what is happening before reading the walkthrough.
Example 1: Google Sign-In — OAuth2 Login Flow
Scenario: When you click “Sign in with Google,” three systems exchange a precise sequence of messages. This diagram shows that flow — it illustrates how return messages carry data back and why the ordering of messages matters.
What the UML notation captures:
- Three lifelines, one flow:
Browser,AppBackend, andGoogleOAuthare the three participants. The browser intermediates between your app and Google — this is why OAuth feels like a redirect chain. - Solid arrows (synchronous calls): Every
->means the sender blocks and waits for a response before continuing. The browser sends a request and waits for the redirect before proceeding. - Dashed arrows (return messages): The
-->arrows carry responses back — the auth code, the access token, the session cookie. Return messages always flow back to the caller. - Top-to-bottom = time: Reading vertically, you reconstruct the complete OAuth handshake in order. Swapping any two messages would break the protocol — the diagram makes those ordering dependencies visible.
Example 2: DoorDash — Placing a Food Order
Scenario: When a user submits an order, the app charges their card and notifies the restaurant. But what if the payment fails? This diagram uses an alt fragment to model both the success and failure paths explicitly.
What the UML notation captures:
altfragment (if/else): The dashed horizontal line inside the box divides the two branches. Only one branch executes at runtime. When you seealt, thinkif/else.- Guard conditions in
[ ]:[payment approved]and[payment declined]are boolean guards — they must be mutually exclusive so exactly one branch fires. - Different paths, different participants: In the success branch, the flow continues to
Restaurant. In the failure branch, it returns immediately to the app. The diagram makes both paths equally visible — no “happy path bias.” - Why
altand notopt? Anoptfragment has only one branch (if, no else). Because we have two explicit outcomes — success and failure —altis the correct choice.
Example 3: GitHub Actions — CI/CD Pipeline Trigger
Scenario: A developer pushes code, GitHub triggers a build, tests run, and deployment happens only if tests pass. This diagram uses opt for conditional deployment and a self-call for internal processing.
What the UML notation captures:
- Self-call (
build -> build): A message from a lifeline back to itself models an internal call —BuildServicerunning its own test suite. The arrow loops back to the same column. optfragment (if, no else): Deployment only happens if all tests pass. There is no “else” branch — on failure the flow skips theoptblock and continues to the notification.- Return after the fragment:
gh --> dev: notify(testResults)executes regardless of whether deployment occurred — it is outside theoptbox, at the outer sequence level. - Activation ordering:
buildrunsrunTests()before returningtestResultstogh. Top-to-bottom ordering guarantees tests complete before GitHub is notified.
Example 4: Uber — Real-Time Driver Matching
Scenario: When a rider requests a trip, the matching service offers the ride to drivers until one accepts. This diagram shows a loop fragment combined with an alt inside — the most powerful combination in sequence diagrams.
What the UML notation captures:
loopfragment: The matching service repeats the offer-cycle until a driver accepts.loopmodels iteration — equivalent to awhileloop. In practice this loop has a timeout (e.g., 3 attempts before cancellation), which would be the loop guard condition.- Nested
altinsideloop: Each iteration of the loop has its own if/else: did the driver accept or decline? Nesting fragments is valid and common — it directly mirrors nested control flow in code. - Flow continues after the loop: Once a driver accepts, execution exits the
loopand the notification is sent. Messages outside a fragment are unconditional. DriverAppas a participant: The driver’s mobile app is a first-class lifeline. This shows that sequence diagrams can include mobile clients, web clients, and backend services on equal footing.
Example 5: Slack — Real-Time Message Delivery
Scenario: When you send a Slack message, it is persisted, then broadcast to all subscribers of that channel. This diagram shows the fan-out delivery pattern using a loop fragment.
What the UML notation captures:
- Sequence before the loop:
persistand getmessageIdhappen exactly once — before the broadcast. The diagram makes this ordering explicit: a message is saved before it is delivered to anyone. loopfor fan-out delivery: Each online subscriber receives their own delivery call. In a channel with 200 members, the loop body executes 200 times. The diagram abstracts this into a single readable fragment.ackafter the loop: The sender receives their acknowledgment (ack(messageId)) only after the broadcast completes. This is outside the loop — it is unconditional and happens once.WebSocketGatewayas the central hub: All messages flow in and out through the gateway. The diagram shows this hub topology clearly — every arrow touchesws, revealing it as the architectural bottleneck. This is a useful architectural insight visible only in the sequence diagram.
Chapter Summary
Sequence diagrams are a powerful tool to understand the dynamic, time-based behavior of a system.
- Lifelines and Messages establish the basic timeline of communication.
- OPT fragments handle “maybe” scenarios (if).
- ALT fragments handle “either/or” scenarios (if/else).
- LOOP fragments handle repetitive scenarios (while/for).
By mastering these fragments, you can model nearly any procedural logic within an object-oriented system before writing a single line of code.
End of Chapter Exercises (Retrieval Practice)
To solidify your learning, attempt these questions without looking back at the text.
- What is the key difference between an
ALTfragment and anOPTfragment? - If you needed to model a user trying to enter a password 3 times before being locked out, which fragment would you use as the outer box, and which fragment would you use inside it?
- Draw a simple sequence diagram (using pen and paper) of yourself ordering a book online. Include one
OPTfragment representing applying a promo code.
Interactive Practice
Test your knowledge with these retrieval practice exercises. These diagrams are rendered dynamically to ensure you can recognize UML notation in any context.
Knowledge Quiz
UML Sequence Diagram Practice
Test your ability to read and interpret UML Sequence Diagrams.
What type of message is represented by a solid line with a filled (solid) arrowhead?
What does the dashed line in the diagram below represent?
Which combined fragment would you use to model an if-else decision in a sequence diagram?
Look at this diagram. How many times could the ping() message be sent?
Which of the following are valid combined fragment types in UML sequence diagrams? (Select all that apply.)
What does the opt fragment in this diagram mean?
In UML sequence diagrams, what does time represent?
Which arrow style represents an asynchronous message where the sender does NOT wait for a response?
What does an activation bar (thin rectangle on a lifeline) represent?
What is the correct lifeline label format for an unnamed instance of class ShoppingCart?
Given this Java code, which sequence diagram element represents the new Payment(amount) call?
java public void makePayment(int amount) {
Payment p = new Payment(amount);
p.authorize();
}
A sequence diagram and a class diagram are drawn for the same system. An arrow in the sequence diagram shows order -> inventory: checkStock(itemId). What must be true in the class diagram?
Retrieval Flashcards
UML Sequence Diagram Flashcards
Quick review of UML Sequence Diagram notation and fragments.
What is the difference between a synchronous and an asynchronous message arrow?
How is a return message drawn in a sequence diagram?
What is the difference between an opt fragment and an alt fragment?
What does a lifeline represent, and how is it drawn?
Name the combined fragment you would use to model a for/while loop in a sequence diagram.
What does an activation bar (execution specification) represent on a lifeline?
What is the correct naming convention for lifelines in sequence diagrams?
What is the par combined fragment used for?
Pedagogical Tip: If you find these challenging, it’s a good sign! Effortful retrieval is exactly what builds durable mental models. Try coming back to these tomorrow to benefit from spacing and interleaving.
Interactive Tutorials
Master UML sequence diagrams by writing code that matches target diagrams in our interactive tutorials: