Workflow Patterns and Control Flow
Control Flow Overview
Azure Logic Apps provides rich control flow constructs for building complex workflows. Understanding these patterns is essential for production-grade integrations.
Microsoft Reference: Control flow actions in Logic Apps
Condition (If/Else)
Branch your workflow based on a condition:
{
"Condition": {
"type": "If",
"expression": {
"and": [
{
"greater": [
"@body('Get_order')?['total']",
1000
]
}
]
},
"actions": {
"Require_Approval": { }
},
"else": {
"actions": {
"Auto_Approve": { }
}
}
}
}
Compound Conditions
{
"expression": {
"or": [
{
"and": [
{ "equals": ["@triggerBody()?['priority']", "high"] },
{ "greater": ["@triggerBody()?['amount']", 5000] }
]
},
{
"equals": ["@triggerBody()?['escalated']", true]
}
]
}
}
Microsoft Reference: Conditional statements
Switch (Multi-Path Branching)
Route workflow execution based on a value:
{
"Switch": {
"type": "Switch",
"expression": "@triggerBody()?['messageType']",
"cases": {
"Order": {
"case": "order",
"actions": {
"Process_Order": { }
}
},
"Invoice": {
"case": "invoice",
"actions": {
"Process_Invoice": { }
}
},
"Return": {
"case": "return",
"actions": {
"Process_Return": { }
}
}
},
"default": {
"actions": {
"Send_to_Dead_Letter": { }
}
}
}
}
Microsoft Reference: Switch statements
For Each Loop
Iterate over an array of items:
{
"For_each_order": {
"type": "Foreach",
"foreach": "@body('Get_orders')?['value']",
"actions": {
"Process_Order": {
"type": "Http",
"inputs": {
"method": "POST",
"uri": "https://api.example.com/process",
"body": "@items('For_each_order')"
}
}
},
"runtimeConfiguration": {
"concurrency": {
"repetitions": 20
}
}
}
}
Concurrency Control
| Setting | Value | Behaviour |
|---|---|---|
repetitions |
1 |
Sequential processing (ordered) |
repetitions |
20 |
Up to 20 iterations in parallel (default) |
repetitions |
50 |
Maximum parallelism (Consumption) |
Accessing Loop Context
// Current item
@items('For_each_order')
// Current item property
@items('For_each_order')?['orderId']
// Current iteration index (Standard only)
@iterationIndexes('For_each_order')
Nested For Each
For_each_customer
→ Get_orders for this customer
→ For_each_order
→ Get_order_lines for this order
→ For_each_line
→ Process line item
Caution: Nested For Each loops can generate a large number of actions. Monitor execution counts and consider batching or chunking large datasets.
Microsoft Reference: For Each loops
Until Loop
Repeat actions until a condition is met:
{
"Wait_for_approval": {
"type": "Until",
"expression": "@or(equals(body('Check_status')?['status'], 'approved'), equals(body('Check_status')?['status'], 'rejected'))",
"limit": {
"count": 60,
"timeout": "PT1H"
},
"actions": {
"Check_status": {
"type": "Http",
"inputs": {
"method": "GET",
"uri": "https://api.example.com/approval/@{triggerBody()?['requestId']}"
}
},
"Delay": {
"type": "Wait",
"inputs": {
"interval": {
"count": 1,
"unit": "Minute"
}
}
}
}
}
}
Until Loop Limits
| Limit | Consumption | Standard |
|---|---|---|
| Maximum count | 5,000 | 5,000 |
| Maximum timeout | PT1H (1 hour) | Configurable |
| Default count | 60 | 60 |
| Default timeout | PT1H | PT1H |
Microsoft Reference: Until loops
Parallel Branches
Execute multiple paths simultaneously:
Trigger
├── Branch 1: Call CRM API → Update customer record
├── Branch 2: Call ERP API → Create invoice
└── Branch 3: Send notification → Teams/Email
→ Join: All branches complete
→ Next action
Workflow Definition
{
"Parallel_Processing": {
"type": "Scope",
"actions": {
"Update_CRM": {
"type": "Http",
"inputs": { "method": "POST", "uri": "https://crm.example.com/update" }
},
"Create_Invoice": {
"type": "Http",
"inputs": { "method": "POST", "uri": "https://erp.example.com/invoice" }
},
"Send_Notification": {
"type": "ApiConnection",
"inputs": { }
}
}
}
}
Actions within a scope that have no runAfter dependencies execute in parallel automatically.
Delay and Schedule Patterns
Fixed Delay
{
"Delay_5_minutes": {
"type": "Wait",
"inputs": {
"interval": {
"count": 5,
"unit": "Minute"
}
}
}
}
Delay Until a Specific Time
{
"Delay_until_9am": {
"type": "Wait",
"inputs": {
"until": {
"timestamp": "@formatDateTime(addDays(startOfDay(utcNow()), 1), 'yyyy-MM-ddT09:00:00Z')"
}
}
}
}
Sliding Window Trigger
Process data in non-overlapping time windows (catches up on missed windows):
{
"triggers": {
"Sliding_Window": {
"type": "SlidingWindow",
"recurrence": {
"frequency": "Hour",
"interval": 1
}
}
}
}
Batching
Batch Sender
Send individual messages to a batch:
{
"Send_to_batch": {
"type": "SendToBatch",
"inputs": {
"batchName": "OrderBatch",
"content": "@triggerBody()",
"host": {
"triggerName": "Batch_trigger",
"workflow": {
"id": "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Logic/workflows/batch-receiver"
}
}
}
}
}
Batch Receiver
Process accumulated messages when a condition is met:
{
"triggers": {
"Batch_trigger": {
"type": "Batch",
"inputs": {
"mode": "Inline",
"batchGroupName": "OrderBatch",
"releaseCriteria": {
"messageCount": 100,
"recurrence": {
"frequency": "Minute",
"interval": 5
}
}
}
}
}
}
Release criteria options:
- Message count — Release when N messages accumulate
- Schedule — Release at regular intervals
- Both — Release when either condition is met (whichever comes first)
Microsoft Reference: Batch processing in Logic Apps
Concurrency and Throttling
Trigger Concurrency
Control how many workflow instances can run simultaneously:
{
"triggers": {
"manual": {
"type": "Request",
"runtimeConfiguration": {
"concurrency": {
"runs": 10
}
}
}
}
}
| Setting | Consumption | Standard |
|---|---|---|
| Maximum concurrent runs | 50 | 100 |
| Default concurrent runs | Unlimited* | 100 |
| Queue depth | 100,000 | 100,000 |
*Unlimited means triggers fire without waiting for previous runs to complete.
SplitOn (Debatching)
Automatically split an array trigger payload into individual workflow runs:
{
"triggers": {
"manual": {
"type": "Request",
"splitOn": "@triggerBody()?['orders']",
"runtimeConfiguration": {
"concurrency": {
"runs": 25
}
}
}
}
}
Each item in the orders array triggers a separate workflow run.
Microsoft Reference: Concurrency, debatching, and loops
Workflow Chaining
Child Workflows
Call one Logic App from another:
{
"Call_child_workflow": {
"type": "Workflow",
"inputs": {
"host": {
"triggerName": "manual",
"workflow": {
"id": "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Logic/workflows/child-workflow"
}
},
"body": {
"orderId": "@triggerBody()?['orderId']",
"customerId": "@triggerBody()?['customerId']"
}
}
}
}
When to Use Child Workflows
- Reusable logic — Share common processing across multiple parent workflows
- Reduce complexity — Break large workflows into manageable pieces
- Independent scaling — Different concurrency settings for parent and child
- Separate error handling — Isolate failure domains
- Team ownership — Different teams manage different workflow components
Long-Running Workflows
Asynchronous Patterns (202 + Polling)
For workflows that take minutes or hours:
1. Client sends request
2. Logic App returns 202 Accepted with Location header
3. Client polls the Location URL
4. Logic App returns 200 OK when complete
// Enable in HTTP trigger
{
"triggers": {
"manual": {
"type": "Request",
"operationOptions": "DisableAsyncPattern" // Return final response synchronously (for short workflows)
}
}
}
Webhook Action Pattern
For human approvals or external callbacks:
{
"Wait_for_callback": {
"type": "HttpWebhook",
"inputs": {
"subscribe": {
"method": "POST",
"uri": "https://approval.example.com/register",
"body": {
"callbackUrl": "@listCallbackUrl()",
"orderId": "@triggerBody()?['orderId']"
}
},
"unsubscribe": {
"method": "POST",
"uri": "https://approval.example.com/unregister"
}
}
}
}
Microsoft Reference: Handle long-running tasks
Trigger Conditions
Add expressions that must evaluate to true for the trigger to fire:
{
"triggers": {
"When_a_message_is_received": {
"type": "ApiConnection",
"conditions": [
{
"expression": "@greater(triggerBody()?['Properties']?['Priority'], 5)"
},
{
"expression": "@not(equals(triggerBody()?['ContentType'], 'test'))"
}
]
}
}
}
This reduces unnecessary workflow runs and saves costs.
Best Practices
- Set concurrency limits on triggers to prevent overwhelming downstream systems
- Use sequential For Each (
concurrency: 1) when order matters or for rate-limited APIs - Implement timeouts on Until loops to prevent infinite loops
- Use SplitOn for array payloads to process items independently
- Break large workflows into child workflows for maintainability
- Use Scope actions to group related actions and simplify error handling
- Add trigger conditions to filter unwanted executions early
- Monitor loop counts — nested loops can generate thousands of actions per run
- Use batching for high-volume, low-latency scenarios
- Implement idempotency — design actions to be safely retried