Durable Functions in Azure — what they are and when to use them
Durable Functions is an extension to Azure Functions that lets you write stateful workflows as code. The runtime persists state, replays the orchestrator on each step, and handles checkpointing — so workflows survive crashes, redeploys, and waits of hours, days, or weeks.
The problem they solve
A standard Azure Function is stateless. If you need a workflow like:
Order placed → wait for payment → wait for shipping → wait 7 days → send review request
You'd have to manually coordinate with queues, timers, durable storage, and recovery logic. Each step needs its own function, plus glue.
Durable Functions lets you write it as one continuous-looking method that the runtime quietly persists between steps:
[Function("OrderWorkflow")]
public async Task<string> RunAsync([OrchestrationTrigger] TaskOrchestrationContext ctx)
{
var order = ctx.GetInput<Order>()!;
var reservation = await ctx.CallActivityAsync<Reservation>("ReserveStock", order.Items);
var payment = await ctx.CallActivityAsync<Payment>("ChargeCard", order);
var shipment = await ctx.CallActivityAsync<Shipment>("Ship", order);
// Wait up to 7 days for the customer to receive their order
await ctx.CreateTimer(TimeSpan.FromDays(7), CancellationToken.None);
await ctx.CallActivityAsync("SendReviewRequest", order.CustomerId);
return "completed";
}
That CreateTimer(7 days) does NOT block a function instance for 7 days. The orchestrator is unloaded after each step and replayed when the next event fires. You pay nothing during the wait.
The four function types in Durable
| Type | Purpose | Lifetime |
|---|---|---|
| Client | Starts orchestrations from HTTP / queue / timer | Standard short-lived function |
| Orchestrator | Defines the workflow as code | Replayed on each step (must be deterministic) |
| Activity | Does one unit of work (DB call, API call, computation) | Standard short-lived function |
| Entity | Stateful actor (counter, lock, aggregate state) | Long-lived addressable state |
Real example — SAGA-style order workflow with compensation
public class OrderSaga
{
[Function("OrderSaga_Start")]
public static async Task<HttpResponseData> StartAsync(
[HttpTrigger("post", Route = "orders")] HttpRequestData req,
[DurableClient] DurableTaskClient client)
{
var order = await req.ReadFromJsonAsync<PlaceOrderRequest>();
var instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
"OrderSaga_Orchestrator", order);
return await client.CreateCheckStatusResponseAsync(req, instanceId);
}
[Function("OrderSaga_Orchestrator")]
public static async Task<OrderResult> OrchestrateAsync(
[OrchestrationTrigger] TaskOrchestrationContext ctx)
{
var order = ctx.GetInput<PlaceOrderRequest>()!;
// Each Call is durable — survives crashes
await ctx.CallActivityAsync("ReserveStock", order.Items);
try
{
await ctx.CallActivityAsync("ChargeCard",
new ChargeRequest(order.CustomerId, order.Total));
}
catch (TaskFailedException)
{
// Compensate
await ctx.CallActivityAsync("ReleaseStock", order.Items);
return new OrderResult("payment_failed");
}
// Wait for external shipping confirmation (could be hours)
var shipped = await ctx.WaitForExternalEvent<ShippedEvent>(
"Shipped", timeout: TimeSpan.FromDays(7));
return new OrderResult("completed");
}
[Function("ReserveStock")]
public static Task ReserveStockAsync(
[ActivityTrigger] List<LineItem> items, FunctionContext ctx)
{
// Real work — DB write, HTTP call to Inventory service, etc.
return ...;
}
}
When to use Durable Functions
| Workflow | Why Durable wins |
|---|---|
| Multi-step orchestration (SAGA, approval chains) | Code-as-workflow with auto-checkpoint |
| Long-running waits (wait for external event, sleep N days) | Pays nothing while waiting |
| Fan-out / fan-in (run 100 activities in parallel, then aggregate) | One-liner with Task.WhenAll over CallActivityAsync |
| Retries with exponential backoff | Built into CallActivityAsync with RetryOptions |
| Human-in-the-loop (approve / reject after 24h) | WaitForExternalEvent cleanly handles this |
When NOT to use them
- Single-step transformations — overhead is not worth it; use plain Functions
- Short fire-and-forget — queues are simpler
- Real-time requirements (< 100 ms response) — orchestrator replay adds latency
- High-frequency state mutations (1000s per second on the same entity) — Entity Functions bottleneck on Storage
The replay model (the tricky part)
Each time an orchestrator runs to the next await, the Durable runtime persists its state to Azure Storage. When the next event fires (timer, activity completion, external event), the orchestrator is REPLAYED from the beginning, with prior await results returned from history.
This means orchestrator code MUST be deterministic:
// ❌ Non-deterministic — different value on replay
var id = Guid.NewGuid();
// ✅ Use the context for deterministic GUIDs
var id = ctx.NewGuid();
// ❌ Non-deterministic — clock changes
var now = DateTime.UtcNow;
// ✅ Use the context
var now = ctx.CurrentUtcDateTime;
// ❌ Non-deterministic — direct DB/API call from orchestrator
var customer = await _db.Customers.FindAsync(id);
// ✅ Wrap in an Activity
var customer = await ctx.CallActivityAsync<Customer>("GetCustomer", id);
This is the #1 cause of bugs in Durable Functions. Orchestrators are not regular code — they're declarative workflows that happen to look like async methods.
Storage backend
Durable Functions stores state in Azure Storage (default) or Microsoft SQL (newer). Storage Account = control queues + history tables + instance tables. For multi-region or high-throughput, plan capacity ahead.
Alternatives — when to look elsewhere
- Temporal.io — language-agnostic, more powerful, cloud-portable. Use if you're not all-in on Azure.
- AWS Step Functions — analogous service on AWS
- Camunda / Cadence — enterprise BPMN engines
Durable Functions is the most Azure-native option and integrates cleanest with the rest of the Azure stack.
Cost note
Durable Functions cost = Azure Functions cost (execution + memory) + Azure Storage cost (transactions + storage).
For a workflow that runs 1M times/month with 5 activities each + 1-day timer wait: roughly ₹500-1500/month including storage. Far cheaper than running a state-machine service yourself.
Interview-grade summary
"Durable Functions lets you write stateful, long-running, multi-step workflows as code on top of Azure Functions. The runtime persists state and replays the orchestrator at each step, so a workflow can wait for hours or days without keeping a process alive. It's the right tool for orchestrated SAGAs, fan-out/fan-in, human-in-the-loop approvals, and any workflow longer than 10 minutes. The trade-off is the replay model — orchestrator code must be deterministic, which is a real constraint."