Loading...

Power Automate concurrency: parallelism, scopes, and retries

A flow that runs 2000 iterations serially at 3 seconds each takes 100 minutes. Bumping the concurrency to 20 takes 5. Getting concurrency right without breaking ordering, retries, or rate limits is the unglamorous work that keeps flows production-viable.

Power Automate concurrency: parallelism, scopes, and retries

A flow that iterates through 2000 records and calls an HTTP endpoint for each, at 3 seconds per call serially, takes 100 minutes. Bumping the apply-to-each concurrency to 20 takes 5 minutes. The 20x speedup is obvious. The parts that are not obvious - how to avoid corrupting order-sensitive logic, how retries interact with parallelism, how to stay under rate limits - are what separate a flow that works in Dev from a flow that survives Prod traffic.

Here are the settings, patterns, and trade-offs we run in production.

Apply-to-each concurrency settings

Every Apply to each action has a concurrency setting in its configuration panel. Default is off (serial execution). The options:

  • Off: iterations run one after another. 2000 items × 3 seconds = 100 minutes.
  • Degree of parallelism (1-50): up to that many iterations run concurrently.

At 50 parallel iterations, a 3-second-per-call loop over 2000 items runs in ~2 minutes. The speedup is real.

But parallel iteration introduces three problems:

  1. Shared variables. Power Automate's Increment variable, Compose, and similar actions are not thread-safe across parallel iterations. Two iterations incrementing the same counter at once can lose updates.
  2. Order dependencies. If iteration N depends on iteration N-1's output (a common pattern in flows that paginate through an API), parallel execution breaks it.
  3. Rate limits. External APIs (Graph, Dataverse, third-party services) have per-second and per-minute call limits. 50 parallel calls in a tight loop can hit them fast.

The three patterns we use

Pattern 1: completely independent iterations

When each iteration is genuinely independent - send an email per recipient, write a log row per event, call an idempotent API - set the concurrency to 20 or 30 and move on. The parallelism is free speedup.

Upper bound: the slower of "the external service's rate limit" and "50 (platform max)."

Pattern 2: iterations with shared state - use Compose and Collect, not variables

Instead of Increment variable inside the loop, use Compose actions that capture per-iteration state, then aggregate outside the loop.

Compose actions scoped inside an Apply to each produce an array of results accessible via body('Apply_to_each') after the loop completes. This is thread-safe and enables concurrent execution.

For counters, use length(...) on the collected results at the end: no shared variable needed.

Pattern 3: ordered iteration with parallelism via batching

When iterations have an order dependency (each call depends on the previous batch's state), one approach is to batch: 2000 items into 20 batches of 100, run the batches serially, run each batch's inner items in parallel.

Outer Apply to each has concurrency off (serial). Inner Apply to each has concurrency 20.

The total time is dominated by the sum of batches, but within each batch you get the parallel speedup. A rough 10-20x improvement without breaking order guarantees.

Scope-based error handling

The Scope action wraps a set of steps and exposes a status (Succeeded, Failed, Skipped, TimedOut). Downstream actions can run based on this status via the "Configure run after" setting.

The pattern we use on anything non-trivial:

The Scope's "Configure run after" lets you wire Catch to only "has failed," and Finally to "is successful, has failed, is skipped, has timed out."

This is the closest Power Automate comes to a try/catch/finally structure in code, and it is essential for any production flow.

Retry patterns

Each HTTP connector action has a "Retry Policy" setting with four options:

  • None: no retry on failure (not usually what you want).
  • Default: exponential backoff, 4 retries over roughly 50 seconds total.
  • Fixed interval: N retries at fixed interval.
  • Exponential interval: N retries, exponential backoff, configurable.

The default policy is fine for most transient failures (network blips, target service 503s). For operations that are expensive or non-idempotent, tune the retry:

  • Idempotent operations: default retry is safe. Each attempt is identical; duplicates are impossible.
  • Non-idempotent operations (like "create a payment"): retries can create duplicates. Either make the operation idempotent via an idempotency key in the request, or set retry to None and handle retries in a higher-level Scope/Catch with explicit state checks.

Rate limit handling

External APIs usually return HTTP 429 with a Retry-After header. Power Automate's default retry respects this header in some cases and not others (connector-specific). For critical integrations we wrap the HTTP action in a Scope with explicit 429 handling:

The complication is that Power Automate doesn't have a native loop-until construct for this - you use a Do until with a condition on the result, or a child flow that recurses.

For internal Dataverse calls, the built-in connector handles throttling reasonably. For external connectors, assume nothing and test rate limit behavior with a stress test before going live.

Flow run budget

Power Automate licenses constrain how many flow runs you can execute per 24-hour window. Per-user plans and per-flow plans have different numbers; check the current Microsoft pricing for exact caps.

A flow that runs every 5 minutes and iterates over 1000 items with a nested loop can rack up tens of thousands of child action runs per day. Each counts toward your license's action quota.

Before committing to a high-volume design, project the run count:

  • Trigger frequency: N per day
  • Outer iterations per trigger: M
  • Inner iterations per outer: K
  • Total actions per day: N × M × K × (actions per inner iteration)

If this number is above 10,000-20,000, reconsider whether a single flow is the right pattern. Breaking work into child flows (the "solution flow" pattern) can distribute the action count across licenses.

The pattern we ship for batch processors

Template we reuse for "read 10,000 items, process each, write results":

  1. Trigger: scheduled or HTTP
  2. Scope Try:List items (paginated, using OData @odata.nextLink)Apply to each page (serial, to respect order), inside:Apply to each item in page (concurrency 20):Scope: process itemHTTP or Dataverse callCompose per-item resultIf Scope failed: log per-item failure, do not terminate flow
  3. Scope Catch (runs if Try failed):Log flow-level failureSend alert
  4. Scope Finally:Summarize success/failure countsWrite to run history table

This template handles rate limits via concurrency 20, retries via default policy, per-item failures via nested scopes, and flow-level failures via the outer scope. It is boring, and that is the point.

The three things we check before shipping

Before a new flow goes to Prod:

  1. Test with 10x the expected volume. If the flow is supposed to handle 500 daily events, test with 5000 one-shot. This catches rate limit and performance issues that do not surface at normal volume.
  2. Force a failure. Temporarily break the external API URL or credentials. Verify Catch fires, alert goes out, flow exits cleanly. Too many flows have un-exercised error paths.
  3. Check the action count. Look at the run detail after a realistic test. How many actions does one run consume? Multiply by expected frequency. Confirm it fits your license budget.

An hour of this testing prevents a bad night on call when the flow goes live.

Contact Us Now

Share Your Story

We build trust by delivering what we promise – the first time and every time!

We'd love to hear your vision. Our IT experts will reach out to you during business hours to discuss making it happen.

WHY CHOOSE US

"Collaborate, Elevate, Celebrate where Associates - Create Project Excellence"

SapotaCorp beyond the IT industry standard, we are

  • Certificated
  • Assured quality
  • Extra maintenance

Tell us about your project

close