In the first part of this series, we covered what Mermaid is and why text-based diagrams belong in your workflow. Now it’s time to get practical. This article walks through the two diagram types you’ll reach for most often: flowcharts and sequence diagrams.
We’ll go from basic linear flows to nested architectural diagrams and full OAuth 2.0 sequences. Each level builds on the previous one, so the examples get progressively more complex.
Flowcharts
Flowcharts are the workhorse of technical documentation. They describe processes, decision trees, system architectures, and just about any “thing A connects to thing B” relationship.
Every flowchart starts with the flowchart keyword and a direction:
TDorTB— top to bottom (vertical)LR— left to right (horizontal)BT— bottom to topRL— right to left
Shapes you’ll actually use
| Shape | Syntax | Typical use |
|---|---|---|
| Rectangle | A[Text] | Action / step |
| Rounded | A(Text) | State / event |
| Stadium | A([Text]) | Start / end |
| Diamond | A{Text} | Decision |
| Hexagon | A{{Text}} | Preparation |
| Parallelogram | A[/Text/] | Input / output |
| Circle | A((Text)) | Connector |
| Double circle | A(((Text))) | Event trigger |
| Cylinder | A[(Text)] | Database / storage |
Arrow types
-->solid arrow---line without arrowhead-.->dashed arrow==>thick arrow-->|label|arrow with text--label-->alternative label syntax
Level 1: A simple linear process
The simplest flowchart is a straight chain of actions. No decisions, no branches. Just steps in order.
```mermaid
flowchart TD
A([Start]) --> B[Receive order]
B --> C[Process payment]
C --> D[Ship product]
D --> E([End])
```
That’s it. Stadium shapes mark the start and end, rectangles mark the actions. If your process really is this simple, a flowchart is almost overkill, but it still documents the flow in a way that survives team turnover.
Level 2: Decisions and loops
Most real processes have branching logic. Use diamond shapes for decisions and add labeled arrows for each path.
```mermaid
flowchart TD
A([Start]) --> B[User enters login and password]
B --> C{Credentials valid?}
C -->|Yes| D[Grant access]
C -->|No| E[Show error]
E --> F{Attempts < 3?}
F -->|Yes| B
F -->|No| G[Lock account]
D --> H([End])
G --> H
```
The arrow from F back to B creates a loop. Users keep trying until they either succeed or get locked out after 3 failed attempts. This is a common pattern for any retry logic.
Level 3: Subgraphs for logical grouping
Once your diagram has more than 8-10 nodes, it gets hard to read without structure. subgraph lets you group related nodes into named sections.
```mermaid
flowchart TB
A([Start]) --> B{Order type?}
B -->|Physical| C[Check warehouse stock]
B -->|Digital| D[Generate license key]
subgraph warehouse [Warehouse]
C --> C1{In stock?}
C1 -->|Yes| C2[Reserve item]
C1 -->|No| C3[Order from supplier]
C3 --> C4[Wait for delivery]
C4 --> C2
end
subgraph digital [Digital Processing]
D --> D1[Create license]
D1 --> D2[Send via email]
end
C2 --> E[Generate invoice]
D2 --> E
E --> F[Send confirmation to customer]
F --> G([End])
```
The subgraph syntax is subgraph id [Display Label] … end. The id is a single word used internally; the label in brackets is what gets displayed. Arrows can cross subgraph boundaries freely.
Level 4: Nested subgraphs for architecture
For larger systems, you’ll want subgraphs inside subgraphs. This works well for showing layered architectures where a backend has multiple services, each with their own internal components.
```mermaid
flowchart TB
User([User]) --> LB[Load Balancer]
subgraph frontend [Frontend Layer]
LB --> Web1[Web Server 1]
LB --> Web2[Web Server 2]
LB --> Web3[Web Server 3]
end
subgraph backend [Backend Layer]
subgraph auth [Auth Service]
Auth[Auth API] --> JWT[JWT Token Service]
Auth --> OAuth[OAuth Provider]
end
subgraph core [Core Service]
API[REST API] --> BL[Business Logic]
BL --> Valid[Validator]
BL --> Transform[Data Transformer]
end
subgraph notifications [Notification Service]
NQ[Notification Queue] --> Email[Email Sender]
NQ --> SMS[SMS Gateway]
NQ --> Push[Push Notifications]
end
end
subgraph data [Data Layer]
DB[(PostgreSQL Primary)]
DBR[(PostgreSQL Replica)]
Cache[(Redis Cache)]
S3[(S3 Object Storage)]
DB -.-> DBR
end
Web1 & Web2 & Web3 --> Auth
Auth -->|"Token valid"| API
BL --> DB
BL --> Cache
BL --> S3
BL --> NQ
API --> DBR
```
A few things to notice here:
- The
backendsubgraph contains three nested subgraphs:auth,core, andnotifications - The
&operator (Web1 & Web2 & Web3 --> Auth) connects multiple sources to one target in a single line - Dashed arrows (
-.->) indicate replication between the primary DB and replica - Cylinder shapes (
[()]) represent data stores
Level 5: A CI/CD pipeline
Here’s a realistic CI/CD pipeline showing the full path from a Git push through build, test, and deploy stages with rollback handling.
```mermaid
flowchart LR
A([Push to Git]) --> B[Lint and Format Check]
B --> C[Unit Tests]
C --> D{Tests passed?}
D -->|No| E[Send report to developer]
E --> A
D -->|Yes| F[Build Docker Image]
F --> G[Push to Registry]
G --> H{Branch?}
H -->|develop| I[Deploy to DEV]
H -->|staging| J[Deploy to STAGING]
H -->|main| K[Deploy to PRODUCTION]
I --> L[Smoke Tests DEV]
J --> M[Integration Tests STAGING]
K --> N[Health Check PROD]
L --> O{OK?}
M --> P{OK?}
N --> Q{OK?}
O -->|No| R[Rollback DEV]
P -->|No| S[Rollback STAGING]
Q -->|No| T[Rollback PROD]
O -->|Yes| U([Deployed DEV])
P -->|Yes| V([Deployed STAGING])
Q -->|Yes| W([Deployed PROD])
T --> X[Alert On-Call Team]
```
This example uses the LR direction because pipelines naturally flow left to right. The branching at the H node shows how the same pipeline can deploy to different environments based on the Git branch. Failed health checks trigger rollbacks, and production rollbacks also alert the on-call team.
Sequence Diagrams
Sequence diagrams show interactions between participants over time. They read top-to-bottom, with each horizontal arrow representing a message or API call. When you need to document who calls whom and in what order, this is the right tool.
Core syntax
| Element | Syntax | Purpose |
|---|---|---|
| Participant | participant A | Declare a participant |
| Actor | actor A | A human participant |
| Solid arrow | A->>B: text | Synchronous call |
| Dashed arrow | A-->>B: text | Response / async |
| Activation | activate A / deactivate A | Show active period |
| Short activation | A->>+B: text / B-->>-A: text | + activates, - deactivates |
| Note | Note over A,B: text | Comment above participants |
| Loop | loop Label ... end | Repeated block |
| Alt/else | alt Cond ... else ... end | Conditional branching |
| Opt | opt Cond ... end | Optional block |
| Par/and | par Label ... and ... end | Parallel execution |
| Critical | critical ... option ... end | Critical region |
| Break | break Cond ... end | Exception flow |
Level 1: Simple request-response
The most basic sequence: one participant calls another and gets a response.
```mermaid
sequenceDiagram
actor User as User
participant Server as Server
User->>Server: GET /api/products
Server-->>User: 200 OK (product list)
```
Solid arrows go forward (requests), dashed arrows come back (responses). The as keyword lets you set display names while keeping short IDs for the arrows.
Level 2: Auth flow with conditions
Real-world sequences usually have branching. The alt / else blocks show different paths depending on conditions.
```mermaid
sequenceDiagram
actor User as User
participant App as Application
participant Auth as Auth Service
participant DB as Database
User->>App: Enter email and password
App->>Auth: POST /auth/login
Auth->>DB: SELECT user WHERE email = ?
alt User found
DB-->>Auth: User record
Auth->>Auth: Verify password hash
alt Password correct
Auth-->>App: 200 OK + JWT Token
App-->>User: Redirect to Dashboard
else Password incorrect
Auth-->>App: 401 Unauthorized
App-->>User: Show error
end
else User not found
DB-->>Auth: null
Auth-->>App: 404 Not Found
App-->>User: Show error
end
```
Notice the nested alt blocks. The outer one checks if the user exists; the inner one checks if the password is correct. Each branch shows a different response path.
Level 3: Microservices with parallel processing
When services run in parallel, use par / and blocks. Combined with activate / deactivate, you get a clear picture of what’s happening concurrently.
```mermaid
sequenceDiagram
actor Client as Client
participant GW as API Gateway
participant OrderSvc as Order Service
participant PaySvc as Payment Service
participant InvSvc as Inventory Service
participant NotifSvc as Notification Service
participant DB as Database
Client->>GW: POST /orders (items, address)
activate GW
GW->>OrderSvc: Create order
activate OrderSvc
OrderSvc->>DB: INSERT order
DB-->>OrderSvc: order_id 12345
par Parallel verification
OrderSvc->>PaySvc: Authorize payment
activate PaySvc
PaySvc->>PaySvc: Check limits
PaySvc-->>OrderSvc: payment_authorized true
deactivate PaySvc
and
OrderSvc->>InvSvc: Check availability
activate InvSvc
InvSvc->>DB: SELECT stock WHERE product_id IN (...)
DB-->>InvSvc: stock_data
InvSvc-->>OrderSvc: all_available true
deactivate InvSvc
end
alt All confirmed
OrderSvc->>DB: UPDATE order SET status confirmed
par Notifications
OrderSvc->>NotifSvc: Send confirmation
activate NotifSvc
NotifSvc-->>Client: Email Order 12345 confirmed
deactivate NotifSvc
and
OrderSvc->>InvSvc: Reserve items
InvSvc->>DB: UPDATE stock SET reserved true
end
OrderSvc-->>GW: 201 Created (order_id 12345)
else Payment or stock failed
OrderSvc->>DB: UPDATE order SET status failed
OrderSvc-->>GW: 400 Bad Request
end
deactivate OrderSvc
GW-->>Client: Response
deactivate GW
```
This diagram packs a lot in:
par/andshows payment authorization and inventory checks running simultaneouslyactivate/deactivatebars show how long each service is busy- A nested
parinside the successaltbranch shows notifications and inventory reservation happening in parallel - The failure path is compact since there’s less to do
Level 4: OAuth 2.0 with PKCE
For the most complex example, here’s a full OAuth 2.0 authorization code flow with PKCE, including MFA handling, token refresh, and error recovery.
```mermaid
sequenceDiagram
actor User as User
participant Browser as Browser
participant App as Client App
participant AuthServer as Authorization Server
participant ResServer as Resource Server
participant TokenStore as Token Storage
User->>Browser: Click Sign in with Google
Browser->>App: Initiate OAuth flow
App->>App: Generate state + code_verifier (PKCE)
App->>Browser: Redirect to Authorization Server
Browser->>AuthServer: GET /authorize?response_type=code&client_id=...
AuthServer->>Browser: Show login form
User->>Browser: Enter credentials
Browser->>AuthServer: POST /login (email, password)
critical Credential verification
AuthServer->>AuthServer: Validate + MFA check
option MFA enabled
AuthServer->>User: Send OTP code
User->>Browser: Enter OTP
Browser->>AuthServer: POST /verify-mfa
option MFA disabled
AuthServer->>AuthServer: Skip MFA
end
AuthServer->>Browser: Redirect callback_url?code=AUTH_CODE&state=...
Browser->>App: Callback with authorization code
App->>App: Verify state parameter
App->>AuthServer: POST /token (code, code_verifier, client_id)
activate AuthServer
AuthServer->>AuthServer: Verify code + PKCE challenge
AuthServer-->>App: access_token + refresh_token + id_token
deactivate AuthServer
App->>TokenStore: Store tokens (encrypted)
loop Every API request
App->>TokenStore: Get access_token
TokenStore-->>App: access_token
App->>ResServer: GET /api/user/profile (Bearer token)
activate ResServer
break Token expired (401)
ResServer-->>App: 401 Unauthorized
App->>AuthServer: POST /token (refresh_token)
AuthServer-->>App: new access_token
App->>TokenStore: Update access_token
App->>ResServer: Retry request with new token
end
ResServer-->>App: 200 OK (user data)
deactivate ResServer
App-->>Browser: Display data
end
```
This one uses every major sequence diagram feature:
critical/optionfor the MFA decision pointbreakfor handling expired tokens mid-looploopfor repeated API callsactivate/deactivatefor showing service activity periods
It’s a realistic representation of how modern OAuth implementations actually work, including the token refresh mechanism that trips up a lot of developers.
Interactive playground
Want to try editing Mermaid syntax and see the result live? Use the component below to experiment with any of the examples from this article.
Practical tips for better diagrams
After building dozens of these, a few patterns consistently produce cleaner results:
- Keep node IDs short. Use
Auth,DB,GWinstead ofAuthorizationService,PostgreSQLDatabase,APIGateway. The display label can be long; the ID should be fast to type and read. - Pick direction deliberately. Processes and pipelines read well left-to-right (
LR). Hierarchies and decision trees work better top-to-bottom (TD). Architecture diagrams depend on what you’re emphasizing. - Don’t over-detail. If a sequence diagram has 15 participants, it’s probably documenting too much in one diagram. Split it into two or three focused diagrams instead.
- Use subgraphs for cognitive grouping, not just aesthetics. A subgraph should represent a real boundary, like a service, a team, or a deployment tier.
- Label your arrows. Unlabeled arrows are fine for simple flows, but once you have more than 5-6 connections, labels prevent misreading.
What’s next
The final article in this series covers Gantt charts for project planning, along with advanced Mermaid tips like themes, excludes weekends, task statuses, and when to pick each diagram type.