A sequence diagram alt block is a real fork. One branch runs, and the other branches do not. opt is not a smaller alt. opt is a single branch that may not run, with no paired alternative. else belongs to alt. It does not belong to opt. If you remember those three, the rest of the sequence syntax is bookkeeping.
I used opt for months as "alt, but I'm in a hurry." The pictures looked fine. The reviews didn't. A reader would ask what happens when the optional step is skipped, and the diagram had no answer, because opt isn't required to show the skip. Sometimes that silence is correct. Sometimes I had hidden a second path I needed on the page. The fix was to switch the block to alt and write the other side, not to add more arrows inside the opt.
Alt is a fork
alt opens the fork. else opens the next branch. end closes the whole fork. You can have more than one else. Exactly one of those branches is the story for a given run. The diagram shows all of them because the reader needs every story, not because one request does every story.
The text after alt is the condition for the first branch. The text after else is the condition for the next one. Keep them short and mutually exclusive. alt approved and else declined are a fork. alt approved and else also maybe approved are a muddle wearing the right keywords. If both can be true, you don't have a fork. You have two facts, and they might be two separate messages rather than an alt.
Put the messages that differ inside the branches. Put the messages that always happen outside the block. I used to repeat the final reply in every branch and also after the end, and the diagram said we replied twice. The reply belongs in one place. If both branches reply, each reply sits inside its branch and there is no third reply after end. If only the success branch replies, the failure branch should show what it does instead, even if that is a single "reject" message. An empty branch looks like you forgot it.
The sequence diagram syntax page covers participants, arrow shapes, and the rest of the blocks. This page stays on the fork. The sequence tutorial is the longer walk from a blank file to a full handshake. Use it when you don't yet know how to declare a participant. Stay here when the participants are fine and the branches are wrong.
Opt is a maybe
opt opens one branch. end closes it. There is no else. The messages inside run only when the condition is true. When the condition is false, the diagram says nothing, and that silence means "this part does not happen." It does not mean "something else happens that I didn't draw."
That silence is the whole distinction. Use opt for a side effect that some requests skip and that has no alternative work. Saving a card. Attaching a note. Notifying a channel if a flag is on. The request is still successful either way, and the skipped case doesn't send a different message. It sends fewer messages.
Don't use opt for a failure. A failure is a different story, and readers look for it. If the card charge can be declined, that's an alt, because the declined path has its own replies. Wrapping the decline in opt labeled "if it fails" hides the success path's shape and makes the failure look optional in the English sense of "minor." Optional in this keyword means "might not run." It doesn't mean "unimportant." I labeled an opt "handle the error" and a reader thought error handling was a bonus feature. We rewrote it as alt the same day.
A concrete tell: if you feel the urge to write else under an opt, you wanted alt. The parser will reject the else or attach it in a way you didn't mean, depending on the build. Even if it parses, the keyword is a confession. I wrote else under opt because my fingers had just finished an alt. The preview failed on else, and the error felt unfair. It wasn't. opt has one path. Close it with end and open an alt if you need two.
Else belongs to alt
else doesn't open a block by itself. It continues an alt. An else with no alt above it is a parse error, or it's about to be one. I treat a bare else as a sign I deleted the alt line and left the rest. Put the alt back, or remove the else and use opt if there's truly one branch.
You can nest. An opt inside an alt branch means "on this fork, there is also something that might not run." An alt inside an opt means "when this optional part runs, it has a fork of its own." Both are legal. Both get hard to read past one level. I nest one deep and no further. If I need a second level, I split the picture or I move the inner decision to a flowchart and leave the sequence as the happy conversation plus one fork. The loop form is the other block people nest everywhere. A loop inside an alt is readable. An alt inside a loop inside an opt is a puzzle. Don't make the reviewer solve a puzzle to find out we retry the charge.
end closes the nearest open block. Every alt and every opt needs its own end. Missing one makes the rest of the diagram part of the branch, so a later message looks conditional when it always runs. Extra end is a parse error. I count the opens and the closes before I look at the picture. The picture can look plausible with the wrong span, because the lifelines still show up.
Participants stay declared before the first message, in the left-to-right order you want. Branches don't reorder the lifelines. If Bank only appears inside an else, declare Bank at the top anyway. Otherwise the first time the name appears is inside the branch, and the column order depends on a path. I like the columns stable across the fork so a reader can compare the two stories without the actors jumping.
A charge that forks, and a coupon that might not
The charge is an alt. Approved and declined are both real, and they produce different replies. The coupon is an opt. Some shops attach a coupon after a successful charge, and some requests have no coupon. There is no "else no coupon" message. Nothing happens. That's why it isn't a third else on the charge.
sequenceDiagram
participant Shop as Shop
participant Pay as Pay
participant Bank as Bank
Shop->>Pay: Charge card
Pay->>Bank: Authorize
alt approved
Bank-->>Pay: OK
Pay-->>Shop: Paid
opt coupon on file
Shop->>Pay: Apply coupon
Pay-->>Shop: Adjusted total
end
else declined
Bank-->>Pay: No
Pay-->>Shop: Fail
endRead the nesting. The opt sits inside approved, before that branch's end, and before else declined. A declined charge does not apply a coupon. I had an earlier version with the opt after the whole alt, which said we might apply a coupon to a failure. The code didn't do that. The diagram did, because "after the fork" felt like a tidy place for a side quest. Side quests still have preconditions. Put them in the branch that meets the precondition.
The dotted arrows are replies. The solid arrows are requests. That habit matters more inside a fork than on a straight line, because a reader scanning only the success branch will treat every solid arrow as new work. A dotted OK is the bank answering, not the bank starting a second charge. The example sequence pages are full of straight-line conversations. The fork is the part those examples get to skip. Don't copy a straight line and then wrap all of it in alt. Wrap the part that differs.
autonumber is optional. I left it off here so the branch labels stay the loudest text. If a review comment needs to point at "the coupon," the condition text is the handle. If the thread is long enough that people are counting arrows, add autonumber at the top and don't also type numbers into the messages.
A login that forks, and a code that might not be sent
Password check is a fork. Wrong password rejects. Right password continues. The second factor is an opt on the success branch: some accounts have it, some don't. When they don't, we don't draw an "else skip the code" message. The session is already started. The code is extra.
sequenceDiagram
participant User as User
participant App as App
participant Mail as Mail
User->>App: Submit password
alt password matches
App-->>User: Session started
opt two factor on
App->>Mail: Send code
Mail-->>User: Code
User->>App: Submit code
App-->>User: Code accepted
end
else password wrong
App-->>User: Reject
endMail is declared even though it only appears inside the opt. The column is there on the runs that never email anyone. That's correct. Hiding Mail until the opt makes the optional path reshuffle the picture, and a reviewer comparing the two branches has to relearn the layout. Stable columns, conditional messages.
The reject branch does not send a code. Don't "be complete" by adding the email there. Completeness is drawing the paths that exist. A code on a failed password would be a product bug. I added it once under the theory that both branches should look equally detailed. Equal detail isn't a goal. Accurate detail is.
If two-factor failure needs its own story, the opt is no longer enough. A wrong code is a fork inside the opt: accept versus reject the code. That is the one level of nesting I still tolerate. If the code can also be resent, stop. A resend is a loop, and a loop inside a fork inside an optional block is the puzzle I warned about. Split "resend the code" into a second diagram, or describe it under the figure in a sentence. The user story version of this work is where extra plot usually enters. Stories mention every exception. The diagram should mention the exceptions that change who gets a message.
A free sequence diagram maker will get you lifelines on a page. It will not decide alt versus opt for you. If the draft wraps the entire login in one opt called "if the user exists," read it as a question, not as a result. Users who don't exist are usually a fork with a specific reply, because the product does something visible. That's alt.
Nesting without making the reader keep a stack
Write the condition as the question you would actually ask. "Approved" is better than "condition 1." "Coupon on file" is better than "optional step." The label is the only documentation the branch has once the picture is copied into a slide without the paragraph. I assume the paragraph gets left behind. The label has to survive.
Don't put a novel on the arrow inside the branch. "Authorize" is enough. The response code and the idempotency key belong under the figure. Inside alt, long labels collide with the branch box and the next lifeline. The fork gets wider and the condition gets harder to see, which defeats the block.
Order the branches the way you want them read. I put the success branch first because that's the path a new reader uses to learn the feature, and the failure branch second because it's a delta against the first. If the failure is the point of the review, flip them. There's no rule that alt starts with the happy path. There is a rule that else is the other path, not a footnote. Don't put the real behavior in else and a legacy behavior in alt without saying so in the labels. I did that during a migration and everyone implemented the legacy path, because it was on top.
The OAuth sequence template is a longer handshake that uses these blocks on an authorization-code flow. Compare its forks to yours if you're not sure whether a step is optional. OAuth has real alt cases, like the user denying consent. Denial isn't an opt. The client has to handle a different reply. That template is a good check against the "I'll just use opt" habit.
The AI sequence generator will draft participants from a paragraph. Tell it which outcomes exist. If you only describe the happy path, you'll get a straight line, and you'll wrap it in opt later out of guilt. Describe the decline, and describe the coupon as skippable. Then check that the decline became alt and the coupon became opt. Generators swap them. The swap still parses.
Use alt when both paths are real
Before you commit, read every block as a sentence. "Either we get Paid or we get Fail." "If a coupon is on file, we adjust the total." If the second sentence secretly has an otherwise, change it to alt. If the first sentence has a branch with no messages, you probably don't know what that path does yet. Find out. An empty else is a question mark you drew on purpose, and it's better than a confident opt that hides the question.
Paste the diagram into the editor and toggle one block from opt to alt if you're unsure. The picture will ask you for the other branch by looking unfinished until you write it. That discomfort is useful. Ship the fork that matches the code, including the paths that only send a single refusal.
Related posts
Frequently asked questions
When do I use opt instead of alt?
When there is no else. A fraud check that sometimes runs is opt. A password that matches or does not is alt.
Can else appear without alt?
No. else belongs to alt. The parser will tell you the branch has no question.
Do I need else if one branch is empty?
If the other outcome is 'nothing happens', opt is the honest block. An empty else looks like you forgot the work.