ParallelFork parallel-fork
Split workflow execution into multiple concurrent lanes that run simultaneously. Must always be paired with a ParallelJoin node that waits for all lanes to complete before continuing the workflow.
success port must eventually lead to the paired Join node.
When to Use
- Multi-channel notifications: Send Slack, email, and SMS notifications simultaneously when an event occurs — instead of sequentially, cutting total dispatch time to the slowest channel's latency alone.
- Concurrent API data fetching: Fetch data from 3 independent APIs (credit score, fraud risk, loyalty tier) in parallel to reduce total enrichment time from sum-of-latencies to max-latency.
- Parallel validation checks: Run identity verification, income verification, and address check concurrently for loan applications — all four checks are independent and waiting for each sequentially wastes time.
- Concurrent document generation: Generate invoice PDF, shipping label, and warehouse pick list simultaneously from the same order data, dispatching all to their destinations in parallel.
Configuration
| Field | Required | Description |
|---|---|---|
FailFast |
Optional | Default false. When true, if any single lane fails (throws an unhandled error), all remaining running lanes are cancelled immediately and the parallel block routes to the Join's error port. Use for operations that must all succeed together — e.g. financial debit/credit pairs. |
MaxParallelism |
Optional | Default 0 (unlimited). When set to a positive integer, at most that many lanes run simultaneously. Excess lanes queue and start as running lanes complete. Use when downstream APIs enforce rate limits on concurrent connections. |
Output Ports
| Port | Description |
|---|---|
success | Fires to launch all connected parallel lanes. Every node or branch connected to this port starts execution concurrently. Connect each lane's starting node here. |
OutputData Fields
Available downstream via $output.<forkNodeKey>:
| Field | Type | Description |
|---|---|---|
fork_id | string | The node key of the Fork node — used by the paired Join to identify which parallel block to collect results for. |
lanes_initialized | string | ISO 8601 timestamp of when the parallel lanes were launched. |
Sample Configuration
Three-channel notification fork
{
"FailFast": false,
"MaxParallelism": 0
}
Rate-limited API fan-out (max 2 concurrent)
{
"FailFast": false,
"MaxParallelism": 2
}
Critical paired operations (fail-fast enabled)
{
"FailFast": true,
"MaxParallelism": 0
}
Validation Errors
| Error | Cause |
|---|---|
VAL_INVALID_MAX_PARALLELISM | MaxParallelism is a negative integer. Must be 0 (unlimited) or a positive integer. |
Sample Output
All Branches Port (after all parallel branches complete)
ParallelFork fans out execution to multiple branches simultaneously. When all branches complete, their outputs are collected and available downstream.
{
"_fork": {
"branchCount": 3,
"completedBranches": 3,
"failedBranches": 0,
"totalDurationMs": 1847,
"longestBranchMs": 1650
},
"branches": {
"fetchInventory": {
"status": "completed",
"output": { "totalItems": 1432, "lowStockCount": 12 }
},
"fetchPricing": {
"status": "completed",
"output": { "priceListVersion": "2025-03", "updatedCount": 89 }
},
"fetchSuppliers": {
"status": "completed",
"output": { "activeSuppliers": 24, "pendingOrders": 7 }
}
}
}
Expression Reference
| Expression | Description |
|---|---|
{{ $output.forkNode._fork.branchCount }} | Number of parallel branches that were forked. |
{{ $output.forkNode._fork.failedBranches }} | Number of branches that errored. Check before proceeding. |
{{ $output.forkNode.branches.fetchInventory.output.totalItems }} | Output from a named branch — access by branch name then field path. |
{{ $output.forkNode._fork.totalDurationMs }} | Total wall-clock time for all branches to complete. |
Node Policies and GuardRails
- Always pair with ParallelJoin: Every Fork must have exactly one corresponding Join. A Fork without a Join leaves the workflow in an incomplete state — parallel lanes run but workflow execution never continues past the parallel block.
- Keep lanes independent: Lane subgraphs must not share mutable state via VariableAssignment nodes. Each lane should read input data only and write to its own local outputs. Shared variable writes from concurrent lanes produce race conditions and unpredictable results.
- All Fork branches must connect to the Join: Every branch connected to the Fork's
successport must eventually wire into the paired Join. Branches that end without reaching the Join cause the Join to wait indefinitely. - FailFast for atomic operations: Enable
FailFast: truewhen lanes represent a single atomic business transaction where partial completion is worse than total failure — financial debits/credits, multi-system record creation, distributed lock acquisition. - Use MaxParallelism for rate-limited APIs: When each lane calls the same third-party API, set
MaxParallelismto the API's concurrent request limit to avoid 429 rate-limit errors. - Avoid loops inside parallel lanes without careful design: Placing a Loop node inside a parallel lane is supported but increases memory and thread requirements significantly. Test with representative data volumes before deploying.
Pattern Examples
Pattern 1 — Multi-Channel Order Confirmation
On order confirmation, dispatch notifications via all three channels simultaneously. Total wait time is the slowest channel, not the sum of all three.
FormTrigger (order confirmed)
└─► ParallelFork [key: "notifyFork"]
FailFast: false
└─► success ──┬── EmailSmtp [lane A]
│ To: {{ $json.customer.email }}
│
├── SlackNode [lane B]
│ Channel: #orders-team
│
└── HttpRequest [lane C]
POST /sms/send {{ $json.customer.phone }}
[all three connect to] ──► ParallelJoin [key: "notifyJoin"]
└─► success ──► [continue workflow]
Pattern 2 — Concurrent API Enrichment
Enrich a loan application by calling three independent data sources in parallel. After all respond, the Join merges all results and a downstream node makes the approval decision.
WebhookTrigger (loan application received)
└─► ParallelFork [key: "enrichFork"]
FailFast: true
└─► success ──┬── HttpRequest [key: "creditCheck"]
│ GET /credit-bureau/score/{{ $json.applicantId }}
│
├── HttpRequest [key: "fraudCheck"]
│ GET /fraud-service/risk/{{ $json.applicantId }}
│
└── HttpRequest [key: "incomeCheck"]
GET /income-verify/{{ $json.applicantId }}
[all three connect to] ──► ParallelJoin [key: "enrichJoin"]
└─► success
creditScore: {{ $output.enrichJoin.lane_outputs.lane_creditCheck.score }}
fraudRisk: {{ $output.enrichJoin.lane_outputs.lane_fraudCheck.riskLevel }}
verifiedIncome: {{ $output.enrichJoin.lane_outputs.lane_incomeCheck.annual }}
Pattern 3 — Rate-Limited Batch Fan-Out
Process items in batches with a maximum of 2 concurrent lanes to respect the downstream API's rate limit of 2 concurrent requests.
ParallelFork [key: "batchFork"]
FailFast: false
MaxParallelism: 2
└─► success ──┬── [Lane A: process batch 1]
├── [Lane B: process batch 2]
├── [Lane C: process batch 3] (queues until A or B completes)
└── [Lane D: process batch 4] (queues until a slot opens)
──► ParallelJoin [key: "batchJoin"]