Mermaid call stack and function flow diagrams for code execution tracing
When code has multiple layers of function calls, conditional branching, and recursive functions, following the execution path in your head becomes difficult. A Mermaid diagram that visualizes function calls makes the flow explicit—ideal for code reviews, onboarding, and debugging documentation. This guide shows how to document call stacks, execution order, and function dependencies with Mermaid.
Why visualize function calls
Code execution order is invisible in source code. A call flow diagram reveals:
- Execution sequence — which function calls which, and in what order
- Recursion — how a function calls itself and when it stops
- Conditional branches — which path is taken based on parameters or state
- Call depth — how many layers deep execution goes
- Entry and exit points — where data flows in and out
Without a diagram, a developer reading code must trace through conditionals, loops, and function signatures—a function call diagram does that work upfront.
Basic call flow: sequential functions
Here's the simplest case—a linear chain where A calls B, B calls C:
graph TD
A["main()"]
B["parseInput()"]
C["validateData()"]
D["processData()"]
E["returnResult()"]
A -->|calls| B
B -->|calls| C
C -->|calls| D
D -->|calls| E
E -->|returns| A
The --> arrows show the call direction; |calls| labels clarify the relationship. A developer can trace from main to the bottom without reading code.
Real-world example: user login flow
A login system typically calls several functions. Here's a realistic call stack:
graph TD
A["login()"]
B["validateEmail()"]
C["lookupUser()"]
D["checkPassword()"]
E["hashPassword()"]
F["compareHash()"]
G["createSession()"]
H["returnToken()"]
A -->|1. calls| B
A -->|2. calls| C
C -->|stores user| DB["Database"]
A -->|3. calls| D
D -->|calls| E
E -->|calls| F
F -->|success?| G
G -->|creates| SESSION["Session Store"]
A -->|4. calls| H
style A fill:#4CAF50,stroke:#2E7D32,color:#fff
style H fill:#FFC107,stroke:#F57F17,color:#000
style DB fill:#2196F3,stroke:#1565C0,color:#fff
style SESSION fill:#2196F3,stroke:#1565C0,color:#fff
The numbers (1, 2, 3, 4) show the call order. Side annotations show dependencies on external systems (Database, Session Store). This reveals:
- Email validation happens first.
- Password check is deep (3 functions: validatePassword → hashPassword → compareHash).
- Session creation is separate from the main chain.
Recursion visualization
Recursive functions can be tricky to understand. Here's a Fibonacci example:
graph TD
A["fib(5)"]
B["fib(4)"]
C["fib(3)"]
D["fib(3)"]
E["fib(2)"]
F["fib(2)"]
G["fib(1)"]
H["fib(1)"]
I["fib(0)"]
A -->|calls| B
A -->|calls| C
B -->|calls| D
B -->|calls| E
C -->|calls| F
C -->|calls| G
D -->|calls| H
D -->|calls| I
This shows:
fib(5)branches intofib(4)andfib(3).fib(3)is called twice (redundant computation—a sign to use memoization).- Base cases (
fib(1),fib(0)) are leaves.
A developer immediately sees why naive recursion is slow: duplicated calls. This justifies adding a cache.
Conditional call flow
When a function's behavior depends on input, show decision branches:
graph TD
A["processOrder()"]
B{Is user<br/>premium?}
C["applyPremiumDiscount()"]
D["applyStandardTax()"]
E["calculateShipping()"]
F{Express<br/>shipping?}
G["calculateExpressShipping()"]
H["calculateStandardShipping()"]
I["finalizeOrder()"]
A -->|checks| B
B -->|yes| C
B -->|no| D
C --> E
D --> E
E -->|checks| F
F -->|yes| G
F -->|no| H
G --> I
H --> I
The diamond decision nodes show where execution branches. This is useful for code reviews: "Does this function handle both the premium and standard paths correctly?"
Call tree with parameters
For complex functions, show what data flows through calls:
graph TD
A["getUserProfile(userId=123)"]
B["fetchUser(123)"]
C["User {id,email,name}"]
D["fetchUserPreferences(123)"]
E["Preferences {theme,lang}"]
F["fetchUserOrders(123)"]
G["Orders []"]
H["buildProfile(User,Prefs,Orders)"]
I["Profile {user,prefs,orders}"]
A -->|calls with userId| B
B -->|returns| C
A -->|calls with userId| D
D -->|returns| E
A -->|calls with userId| F
F -->|returns| G
A -->|calls with all data| H
H -->|returns| I
The parameter names and return types make the flow concrete. A reviewer can verify that the right data flows from fetch functions to the builder.
Call stack at a specific moment
When debugging, you want to see the call stack at a breakpoint. Here's a snapshot:
graph TD
A["main()")
B["processRequest(req)"]
C["validateInput(req.body)"]
D["checkLength(data)"]
E["🔴 Breakpoint: checkLength returns false"]
A -->|calls| B
B -->|calls| C
C -->|calls| D
D -->|BREAKPOINT| E
style E fill:#FF5252,stroke:#C62828,color:#fff
Stack trace (bottom-to-top): checkLength ← validateInput ← processRequest ← main. A developer can instantly see where execution stopped and trace back to understand the context.
Async/callback flows
For asynchronous code, show the sequence differently:
graph TD
A["fetchData()"]
B["makeRequest(url)"]
C["Request sent →"]
D["Network delay..."]
E["Server responds ←"]
F["onSuccess(data)"]
G["parseJSON(data)"]
H["updateUI(parsed)"]
I["done()"]
A -->|initiates| B
B -->|async| C
C -->|waiting| D
D -->|received| E
E -->|triggers| F
F -->|calls| G
G -->|calls| H
H -->|callback| I
The async step (C, D, E) shows the non-blocking nature. The callback chain (F → G → H) shows what happens when the response arrives. This helps developers understand "where am I blocked?" vs. "what runs after?"
Best practices for call flow diagrams
- Number the calls — use
1.,2.,3.labels to show execution order explicitly. - Show return data — annotate what each function returns (type or variable name).
- Keep it focused — diagram one user flow or code path, not the entire system at once.
- Use colors by concern — green for success paths, red for errors, blue for external dependencies.
- Avoid deep nesting — if a call stack is more than 6–8 levels deep, split into two diagrams (one for the main flow, one for a deep branch).
- Test against the code — verify the diagram matches the actual code; stale diagrams mislead.
- Add a legend — if you use colors or shapes, explain them at the top.
Comparison table: call flows vs. sequence diagrams
| Format | Best for | Example |
|---|---|---|
| Call flow (graph) | Single-threaded, hierarchical execution | How main() calls down through helper functions |
| Sequence diagram | Multiple actors, message passing | How Service A calls Service B, which calls Service C |
| Flowchart | Decision logic and branching | User login: email → password → session creation |
FAQ
How do I show error paths in a call flow diagram?
Use a red diamond for error decisions, or a red "Error" node. Example: validateInput() -->|error| handleError(). Keep error paths separate from the happy path for clarity.
Can I show timeouts and retries?
Yes. Use a loop annotation or a label: makeRequest() -->|timeout, retry| makeRequest(). Mermaid will render the recursive call. Add a comment to explain the retry count.
What if the same function is called from multiple places?
Show all callers pointing to it. This reveals hot spots (functions called frequently). Example: Both loginUser() and signupUser() call hashPassword().
How do I handle mutual recursion (A calls B, B calls A)?
Show the cycle: A -->|calls| B, B -->|calls| A. It's valid, but add a note explaining the termination condition so readers don't assume infinite loops.
Visualize your function calls in the MermaidCreator editor. Clear call stacks and execution flows make code reviews faster and onboarding easier.
Related posts
Domain modeling with Mermaid class diagrams
Mermaid class diagrams let you sketch and share your object model in plain text — no UML editor, no export step, and no diagram going stale in a forgotten wiki.
Mermaid database transaction flows and query execution diagrams
Visualize database transactions, query sequences, and transaction isolation with Mermaid. Document ACID properties and concurrent database operations.