|   6 minute read


In Part 1, I covered why Durable Functions exists, how starters, orchestrators, and activities work together, and why replay, determinism, and idempotency are so important.

Once that execution model is clear, the next design decision is the shape of the workflow.

Durable Functions gives us a set of well-known patterns for sequencing work, running tasks in parallel, waiting for events, coordinating multiple systems, and managing recurring processes. The trick is not memorising the patterns. It is matching the shape of the pattern to the shape of the problem.

Navigate the Durable Functions Series

Choosing the Right Pattern

The following table is the quick decision guide. The rest of the post explains where each pattern fits and what can go wrong when it is pushed too far.

Pattern Use it when Watch out for
Function Chaining Each step genuinely depends on the result of the previous step Serialising independent work and adding unnecessary latency
Fan-Out/Fan-In Many independent items can run concurrently and their results need to be combined Unbounded fan-out, downstream throttling, large histories, and a heavy fan-in on one orchestrator
Async HTTP API Work outlives a normal HTTP request and the caller can poll or receive a callback Returning internal management URLs without the right security boundary
Monitor An external system must be polled because it cannot notify you Polling forever, polling too frequently, and growing history without ContinueAsNew
External Event or Human Interaction The workflow must wait for an approval, callback, or real-world event Missing correlation, duplicate events, no timeout, or no escalation path
Saga or Compensation Several independent systems must be coordinated without one shared transaction Assuming compensation is a perfect rollback or forgetting to make compensation idempotent
Eternal Orchestration A recurring workflow has no natural end Letting history grow forever instead of periodically using ContinueAsNew
Durable Entity A small, addressable piece of state needs serialised operations Treating an entity as a large or high-throughput general-purpose database
Sub-Orchestration A workflow needs clear phases, reuse, separate histories, or distributed fan-in Splitting so aggressively that tracing and failure handling become harder

Function Chaining Keeps the Sequence Explicit

Function chaining is the simplest orchestration pattern. One activity runs, its result is passed to the next activity, and the process continues in order.

This is a good match for a workflow such as validate, approve, provision, and notify, where each step genuinely depends on the result of the step before it. The orchestration history becomes the reliable record of which steps completed.

Do not chain work simply because it is easier to write. If two steps do not depend on each other, making them run one after another only adds latency.

Fan-Out/Fan-In Needs Backpressure

Fan-out is one of the most useful patterns and one of the easiest to overuse.

Starting one activity per row for a list containing tens of thousands of records creates a large orchestration history and can overwhelm the service being called. Fan-out does not magically create capacity in a database, SaaS API, or downstream queue.

Batch the work, put a sensible upper bound on concurrency, and test against the real downstream limits. For very large workloads, use sub-orchestrations to divide both the fan-out and fan-in burden rather than building one enormous history.

Remember that the individual activities can scale across workers, but the parent orchestrator still has to replay its own history and process the fan-in result. Distributed activity execution does not make the parent history free.

Async HTTP APIs Separate Acceptance from Completion

The async HTTP pattern is useful when an API request starts work that will take longer than a normal HTTP connection should remain open.

Instead of waiting, the starter accepts the request, starts an orchestration, and returns 202 Accepted with a status endpoint. The caller can then check whether the workflow is pending, running, completed, or failed.

This is a much cleaner contract for operations such as provisioning an environment or processing a large export. It also means the management endpoints and instance identifiers become part of the API security design. Do not expose them carelessly.

Monitors Handle Polling Without Holding Compute

The monitor pattern checks an external condition on a schedule by using durable timers. It is useful when another system cannot publish an event or call us back.

Every monitor needs a polling interval, an overall timeout, and a terminal outcome. If it is intended to run forever, periodically use ContinueAsNew to reset the history. If the external system can send a webhook or event, prefer that over unnecessary polling.

External Events Need a Timeout Story

Waiting for an event is easy. Designing what happens when the event never arrives is the real work.

For approvals and callbacks, define:

  • The correlation key.
  • Whether duplicate or late events are ignored.
  • The durable timeout.
  • The escalation or expiry path.
  • What happens if the workflow is suspended, terminated, or restarted.

Use durable timers, not normal delays. If a timer is raced against another durable task and no longer needed, cancel it where the SDK supports cancellation so it does not keep the orchestration open.

Compensation Is a Business Action

A saga is not a distributed database transaction. A compensating action may fail, may need its own retries, and may not return the world to exactly the same state.

For example, refunding a payment is not the same thing as the payment never happening. Releasing stock ten minutes later may affect another order. Compensation therefore needs explicit business rules, observability, and often a manual resolution path.

Eternal Orchestrations Support Recurring Work

An eternal orchestration is a workflow with no natural completion point. At the end of each cycle, it uses ContinueAsNew to begin a new generation with a fresh history.

This is useful for periodic aggregation, recurring cleanup, or a long-lived monitor. It should not be the default for every scheduled task, and it should not become one permanent orchestration per customer without careful scale testing.

Sub-Orchestrations Keep Large Workflows Understandable

A sub-orchestration is an orchestration called by another orchestration. It can give a complex workflow clear phases, reuse a common workflow, isolate part of the history, or distribute a large fan-in.

For example, an onboarding orchestration might call separate identity, account, and notification sub-orchestrations. Each sub-orchestration can have its own input, output, retry, and failure boundary while the parent retains the overall business view.

Durable Entities Provide Small, Addressable State

Durable entities are useful when state belongs to a specific identity and operations against that state must be serialised. Counters, small state machines, locks, and per-resource coordination are common examples.

Entities complement orchestrations; they do not replace them. An orchestration represents a process moving towards an outcome, while an entity represents state that can receive operations over time. Keep entity state small and avoid using it as a general document database.

Final Thoughts

Choosing the correct pattern gives the workflow the right shape, but no pattern is automatically the best answer. Function chaining is not useful when the work is independent. Fan-out is not useful when the dependency cannot handle the concurrency. Polling is not useful when an event is already available. A saga is not useful when one database transaction already solves the problem.

Start with the business process, identify its dependencies and waiting points, and then select the pattern that makes those relationships explicit.

In Part 3, I will move from workflow shape to production readiness: payloads, retries, failures, scaling, history growth, deployment, observability, and testing.

Have you used a Durable Functions pattern that is missing here, or learned a lesson from choosing the wrong one? Please leave a comment or reach out. I would like this series to include the things that only become obvious after running workflows in production.

Further Reading

Leave a comment