Sequence diagrams
Part of the free Low-Level Design course on LogicWiz, in the chapter: Modelling behaviour: sequence and state diagrams. The class-diagram exercises run in the app.
Week 9: Tap Pay, then what?
"Do not show me the classes yet. Draw what happens when the customer taps Pay: who calls whom, in order."
Priya, playing the interviewer in a mock round
A class diagram does not show time
Arjun had a neat class diagram for checkout: CheckoutController, Cart, InventoryService, PaymentGateway. Priya looked at it and asked three questions:
- When is stock reserved: before or after payment?
- If payment fails, who gives the stock back?
- Where is the order saved?
The class diagram could not answer any of them. A class diagram shows structure: which classes exist and which ones know each other. It does not show order: what happens first, what happens next, and what happens when something fails.
For that you draw a sequence diagram. It shows one scenario, such as "customer pays for a cart", as messages between objects over time. Objects sit across the top. Time flows down the page.
The notation, piece by piece
You need only a few symbols. Learn these and you can read almost any sequence diagram.
- Lifeline: a box at the top with the object's name, and a dashed line going down. The line is the object's life during this scenario.
- Message (call): a solid line with a filled arrowhead, from caller to callee. Label it like a method call:
reserve(sku, qty). - Return: a dashed line back to the caller, labelled with what comes back:
okorpaymentId. Many people skip returns for simple calls; draw them when the value matters. - Activation: a thin bar on a lifeline while that object is busy running a method.
- Self-call: an arrow from a lifeline back to itself. The object calls one of its own methods.
- Async message: an open arrowhead. The caller does not wait for an answer, for example when it publishes an event.
Then come the boxes, called fragments, which add logic:
- alt: if and else. The box is split into sections, one per condition. Only one section runs.
- opt: an if with no else. The box runs or is skipped.
- loop: the messages inside repeat, for example once per cart item.
Here is the smallest useful example. Notice the solid arrow for the call and the dashed arrow for the return.
sequenceDiagram
participant C as CheckoutController
participant Cart
C->>+Cart: items()
Cart-->>-C: list of items
The checkout flow
Now the real flow, as Arjun first drew it. Read it top to bottom.
sequenceDiagram
participant App
participant CC as CheckoutController
participant Cart
participant Inv as InventoryService
participant PG as PaymentGateway
App->>CC: checkout(cartId)
activate CC
CC->>Cart: items()
Cart-->>CC: list of items
loop each item
CC->>Inv: reserve(sku, qty)
Inv-->>CC: ok
end
CC->>CC: work out total, fee and coupon
CC->>CC: book delivery slot
CC->>PG: charge(total)
alt payment succeeds
PG-->>CC: paymentId
CC->>CC: save order
CC-->>App: orderId
else payment fails
PG-->>CC: declined
CC->>Inv: releaseAll(items)
CC-->>App: error
end
deactivate CC
The diagram answers Priya's questions at once:
- Stock is reserved before payment, inside the
loop. - If payment fails, the
elsesection releases the stock. Arjun had forgotten this in code; the diagram made the gap visible. - The order is "saved" by a self-call on the controller. Saved where? Nothing on the diagram says.
Reading the diagram for missing classes
Look at the CheckoutController lifeline. It has three self-calls: working out the total with the fee and coupon, booking the slot and saving the order. Each self-call is a job the controller does itself.
Self-arrows piling up on one lifeline are a warning sign. The class is doing work that belongs to other classes. Each self-call that hides a real rule is a candidate for a class of its own:
| Self-call | Missing class |
|---|---|
| work out total, fee and coupon | PriceCalculator |
| book delivery slot | SlotService |
| save order | OrderRepository |
After the change, the controller only coordinates. It sends calculate(cart) to PriceCalculator, book(slotId) to SlotService and save(order) to OrderRepository. Each message becomes a method on the class it points at, and each return label becomes that method's return type. This is how a sequence diagram feeds your class diagram.
Arjun said: "Sequence diagrams are documentation. You draw them after the code works." In an interview the opposite is true. A sequence diagram drawn before the code is a design tool. It finds missing classes, missing error paths and wrong call order in five minutes. Interviewers also ask "walk me through what happens when..." and expect you to answer in this shape, even out loud.
💡 Tip: A lifeline with many outgoing arrows is fine; that object coordinates. A lifeline with many arrows to itself usually hides a class.
Drawing one in an interview
Keep the diagram small and useful:
- One scenario per diagram. Draw the main path and the one failure that matters, in an
altbox. - Five or six lifelines at most. Leave out logging and helpers.
- Label calls as method calls with arguments. They become your method names.
- Show returns when the value matters, such as
paymentIdordeclined. - Use real object names from your class diagram, so the two diagrams agree.
Which diagram should you draw? It depends on the question you are answering.
flowchart TD
Q["What does the interviewer want to see?"] --> A{"Which classes exist and how they relate?"}
A -->|"Yes"| CD["Class diagram"]
A -->|"No"| B{"The order of calls in one scenario?"}
B -->|"Yes"| SD["Sequence diagram"]
B -->|"No"| ST["State diagram of one object's life"]
Practice: draw the classes the diagram reveals
The starter is Arjun's class diagram, with CheckoutService doing all the work the self-calls showed. Add the classes the sequence diagram says are missing, and connect the service to them.
📌 Summary: A sequence diagram shows one scenario over time: solid arrows for calls, dashed for returns, alt for if and else, loop for repeats. Many self-calls on one lifeline point to a missing class.
What Arjun learns next
The payment in this flow has its own life: created, authorised, captured, maybe refunded. Last month a refund went out for a payment that was never captured. Next: state diagrams, guards, and turning the diagram into code.
Check your understanding
- In a sequence diagram, how do you draw PaymentGateway sending paymentId back to the controller after a charge call?
- Checkout must release the reserved stock only if payment fails. Which fragment shows this?
- Arjun's diagram shows five arrows from CheckoutController back to itself. What does that suggest?
- Arjun says: "Sequence diagrams are documentation, so I draw them after the code works." Why is this a weak approach in an LLD round?