low-level-design3 min read

Sequence Diagrams UML Tutorial from Scratch (2026)

Sequence Diagrams UML Tutorial from Scratch (2026)

Published:  |  Category: Low Level Design  |  Reading time: ~15 min
Sequence Diagrams UML Tutorial from Scratch (2026)

Sequence diagrams are interaction diagrams that detail how processes operate over time by ordering messages exchanged between lifelines. They are invaluable for uncovering race conditions, verifying protocol compliance, and documenting complex workflows involving multiple objects or services.

This tutorial covers lifelines, synchronous and asynchronous messages, combined fragments (alt, opt, loop, par), and common interaction patterns. Mastering sequence diagrams will sharpen your ability to reason about temporal system behavior.

Lifelines and Messages

A lifeline represents an individual participant in an interaction, depicted as a rectangle with a dashed vertical line extending below. Messages are arrows between lifelines that represent communication; a filled arrowhead indicates a synchronous call, while an open arrowhead denotes an asynchronous signal. Reply or return messages are shown as dashed arrows.

class Lifeline {
  constructor(name) {
    this.name = name;
    this.messages = [];
  }

  sendSync(target, operation, payload) {
    const msg = { from: this.name, to: target.name, type: "sync", operation, payload };
    this.messages.push(msg);
    console.log(`${this.name} -> ${target.name} : ${operation}()`);
  }

  sendAsync(target, operation, payload) {
    const msg = { from: this.name, to: target.name, type: "async", operation, payload };
    this.messages.push(msg);
    console.log(`${this.name} ->> ${target.name} : ${operation}()`);
  }
}

const client = new Lifeline("Client");
const server = new Lifeline("Server");
const db = new Lifeline("Database");

client.sendSync(server, "placeOrder", { items: [] });
server.sendAsync(db, "saveOrder", { items: [] });

Combined Fragments: alt, opt, and loop

Combined fragments model control flow within sequence diagrams. The alt fragment represents alternative paths (if-else), the opt fragment represents an optional execution (if without else), and the loop fragment handles repetition (while/for). Each fragment is a box with a guard condition in the top-left corner.

class PaymentProcessor {
  async handlePayment(order) {
    if (order.paymentMethod === "credit_card") {
      const valid = await this.validateCard(order.card);
      if (!valid) { throw new Error("Card declined"); }
    }
    if (order.couponCode) {
      order.total = this.applyDiscount(order.total, order.couponCode);
    }
    let attempts = 0;
    let success = false;
    while (attempts < 3 && !success) {
      try {
        await this.chargePayment(order.paymentMethod, order.total);
        success = true;
      } catch (err) { attempts++; }
    }
    return success;
  }
  validateCard(card) { return card.expiry > new Date() && !card.isBlocked; }
  applyDiscount(total, code) { return total * 0.9; }
  chargePayment(method, amount) { console.log("Charged " + amount + " via " + method); }
}

Asynchronous Calls and Reply Patterns

Asynchronous messages do not block the caller; the lifeline continues processing other work. Replies are modeled with a dashed arrow pointing back to the caller. This pattern is common in event-driven architectures, message queues, and non-blocking I/O operations.

class AsyncService {
  async process(data) {
    console.log("[Service] Received request for " + data.id);
    const result = await this.worker.performTask(data);
    console.log("[Service] Sending reply to caller for " + data.id);
    return result;
  }
}

class EventBus {
  constructor() { this.listeners = {}; }
  publish(event, payload) {
    const handlers = this.listeners[event] || [];
    handlers.forEach((h) => { setImmediate(() => h(payload)); });
  }
  subscribe(event, handler) {
    if (!this.listeners[event]) this.listeners[event] = [];
    this.listeners[event].push(handler);
  }
}

const bus = new EventBus();
bus.subscribe("orderPlaced", (order) => {
  console.log("Inventory reserved for order " + order.id);
});
bus.publish("orderPlaced", { id: 1, items: ["laptop"] });

Frequently Asked Questions

What is the difference between a synchronous and asynchronous message in a sequence diagram?

A synchronous message (filled arrowhead) implies the sender waits for a reply before continuing. An asynchronous message (open arrowhead) allows the sender to proceed immediately without waiting for the receiver response.

How do I model a loop with a condition in a sequence diagram?

Use the loop combined fragment with a guard condition in the top-left corner, specifying the iteration criteria such as loop [for each item] or loop [while retries < 3]. The fragment encloses the repeated messages.

What does opt fragment mean in a sequence diagram?

The opt (optional) fragment contains messages that execute only if the specified guard condition is true. It represents an if-then without an else branch. For conditional branches with alternatives, use the alt fragment instead.

Can sequence diagrams represent parallel execution?

Yes, use the par (parallel) combined fragment to show concurrent message flows. Messages within different operands of the par fragment execute concurrently, which is useful for modeling multi-threading or parallel service calls.

Originally published on Ayodhyyya. Last updated June 1, 2026.