Use Case and Activity Diagrams Tutorial from Scratch (2026)
Use case diagrams capture functional requirements by showing actors and their interactions with the system, while activity diagrams model the procedural flow of business processes or algorithms. Together they bridge the gap between stakeholder expectations and system behavior.
This tutorial covers actor identification, use case relationships, activity nodes and edges, decision nodes, fork/join concurrency, and swimlanes for responsibility assignment. You will learn to model end-to-end scenarios effectively.
Actors, Use Cases, and Relationships
An actor is a role played by a human user, external system, or hardware device that interacts with the system. Use cases are ovals containing verb-noun phrases that represent functional goals. Relationships include include (mandatory sub-function), extend (optional behavior), and generalization (parent-child actor or use case).
class Actor {
constructor(name) {
this.name = name;
this.useCases = [];
}
canPerform(useCase) { this.useCases.push(useCase); }
}
class UseCase {
constructor(name) {
this.name = name;
this.includes = [];
this.extends = [];
}
includesUseCase(sub) { this.includes.push(sub); }
extendsWith(extension) { this.extends.push(extension); }
}
const customer = new Actor("Customer");
const placeOrder = new UseCase("Place Order");
const authenticate = new UseCase("Authenticate");
const makePayment = new UseCase("Make Payment");
const applyCoupon = new UseCase("Apply Coupon");
placeOrder.includesUseCase(authenticate);
placeOrder.includesUseCase(makePayment);
placeOrder.extendsWith(applyCoupon);
customer.canPerform(placeOrder);
console.log(customer.name + " can " + placeOrder.name);
Activity Flows and Decision Nodes
An activity diagram begins with an initial node (filled circle), proceeds through action nodes (rounded rectangles), decision nodes (diamonds) with guard conditions, merge nodes, fork/join bars for concurrency, and ends at a final node (filled circle with border). Activities can also accept events and send signals.
class WorkflowEngine {
constructor() { this.nodes = []; }
addNode(name, type) { this.nodes.push({ name, type }); }
execute(initialData) {
let data = initialData;
console.log("Starting workflow...");
if (data.isEligible) {
console.log("Fork: processing concurrently");
const results = [this.validateAddress(data), this.checkInventory(data)];
data = { ...data, validated: results[0], inStock: results[1] };
console.log("Join: all parallel tasks completed");
} else {
throw new Error("Not eligible");
}
console.log("Workflow completed");
return data;
}
validateAddress(data) { console.log("Validating address: " + data.address); return true; }
checkInventory(data) { console.log("Checking stock for item: " + data.item); return true; }
}
const engine = new WorkflowEngine();
engine.execute({ isEligible: true, address: "123 Main St", item: "laptop" });
Swimlanes and Responsibility Partitioning
Swimlanes partition activity diagrams into columns representing different actors, departments, or system components. Each action node belongs to exactly one swimlane, making it clear who is responsible for each step. This is especially valuable in cross-functional workflow modeling.
class Swimlane {
constructor(actor) {
this.actor = actor;
this.activities = [];
}
addActivity(action) {
this.activities.push(action);
console.log("[" + this.actor + "] executes: " + action);
}
}
class CrossFunctionalWorkflow {
constructor() { this.swimlanes = {}; }
addSwimlane(name, actor) { this.swimlanes[name] = new Swimlane(actor); }
run() {
const customer = this.swimlanes["customer"];
const sales = this.swimlanes["sales"];
const warehouse = this.swimlanes["warehouse"];
customer.addActivity("Submit order request");
sales.addActivity("Validate payment details");
sales.addActivity("Approve order");
warehouse.addActivity("Pick and pack items");
warehouse.addActivity("Arrange shipping");
customer.addActivity("Receive shipment confirmation");
}
}
const wf = new CrossFunctionalWorkflow();
wf.addSwimlane("customer", "Customer");
wf.addSwimlane("sales", "Sales System");
wf.addSwimlane("warehouse", "Warehouse System");
wf.run();
Frequently Asked Questions
What is the difference between include and extend in use case diagrams?
Include represents mandatory sub-functionality that is always required when the base use case executes. Extend represents optional behavior that executes only when a specific condition is met. Include points from base to included use case; extend points from extension to base use case.
When should I use an activity diagram instead of a sequence diagram?
Use activity diagrams when modeling business workflows, complex algorithms, or parallel processes where the focus is on flow of control rather than object interactions. Use sequence diagrams when the focus is on message ordering between specific objects over time.
What do fork and join nodes represent in activity diagrams?
A fork node (bar with one incoming edge and multiple outgoing edges) splits a single flow into concurrent flows. A join node (bar with multiple incoming edges and one outgoing edge) synchronizes concurrent flows, waiting for all incoming flows to complete before proceeding.
Can activity diagrams model exception handling?
Yes, exception handlers in activity diagrams are shown as a lightning-bolt edge from an action node to a handler action node. The UML specification also supports interruptible activity regions and exception handlers for comprehensive error flow modeling.
Originally published on Ayodhyyya. Last updated June 1, 2026.