Durable Functions orchestration patterns in Azure - Time & Space Complexity
Start learning this pattern below
Jump into concepts and practice - no test required
When using Durable Functions orchestration patterns, it's important to understand how the number of function calls grows as you add more tasks.
We want to know how the orchestration's execution time changes when the number of activities increases.
Analyze the time complexity of this orchestration pattern that calls multiple activities in sequence.
[FunctionName("Orchestrator")]
public static async Task<List<string>> RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
var results = new List<string>();
var tasks = context.GetInput<List<string>>();
foreach (var taskName in tasks)
{
var result = await context.CallActivityAsync<string>("Activity", taskName);
results.Add(result);
}
return results;
}
This orchestration calls an activity function for each item in a list, one after another.
Identify the API calls, resource provisioning, data transfers that repeat.
- Primary operation: Calling the activity function once per item in the input list.
- How many times: Once for each item in the input list (n times).
Each new item adds one more activity call, so the total calls grow directly with the number of items.
| Input Size (n) | Approx. Api Calls/Operations |
|---|---|
| 10 | 10 activity calls |
| 100 | 100 activity calls |
| 1000 | 1000 activity calls |
Pattern observation: The number of calls grows in a straight line as input size increases.
Time Complexity: O(n)
This means the orchestration time grows directly in proportion to the number of tasks it runs.
[X] Wrong: "Calling multiple activities in sequence runs all at once, so time stays the same no matter how many tasks."
[OK] Correct: Calling activities one after another means each waits for the previous to finish, so total time adds up with each task.
Understanding how orchestration patterns affect execution time helps you design efficient workflows and explain your choices clearly in real projects.
What if we changed the orchestration to call all activities in parallel instead of sequence? How would the time complexity change?
Practice
Solution
Step 1: Understand the function types in Durable Functions
Durable Functions use orchestrator functions to manage workflows and activity functions to perform tasks.Step 2: Identify the role of the orchestrator function
The orchestrator function controls the order and timing of tasks but does not do the actual work itself.Final Answer:
To coordinate and manage the workflow of multiple tasks -> Option BQuick Check:
Orchestrator = workflow manager [OK]
- Confusing orchestrator with activity function
- Thinking orchestrator stores data
- Assuming orchestrator sends notifications
ProcessOrder from an orchestrator function in C#?Solution
Step 1: Recall the syntax for calling activity functions
In Durable Functions, the orchestrator calls activities usingawait context.CallActivityAsyncwith the function name and input.Step 2: Check each option for correctness
await context.CallActivityAsync("ProcessOrder", orderId); uses the correct method with await, context, function name, and input parameter.Final Answer:
await context.CallActivityAsync("ProcessOrder", orderId); -> Option AQuick Check:
Correct async call syntax = await context.CallActivityAsync("ProcessOrder", orderId); [OK]
- Omitting await keyword
- Using wrong method name like CallActivity
- Missing input parameter when required
const outputs = [];
outputs.push(await context.callActivity('TaskA', 1));
outputs.push(await context.callActivity('TaskB', 2));
return outputs;What will the orchestrator return?
Solution
Step 1: Analyze the code execution flow
The orchestrator calls TaskA and waits for its result, then calls TaskB and waits for its result, pushing both into the outputs array.Step 2: Understand the return value
Since both calls are awaited, outputs will contain the results of TaskA and TaskB in order.Final Answer:
An array with results from TaskA and TaskB in order -> Option AQuick Check:
Awaited calls return results in array [OK]
- Assuming only last result is returned
- Thinking outputs is empty without awaits
- Confusing promise with resolved value
public async Task<string> RunOrchestrator(IDurableOrchestrationContext context)
{
var result = context.CallActivityAsync<string>("DoWork", null);
return result.Result;
}What is the problem with this code?
Solution
Step 1: Identify async call usage
The code callsCallActivityAsyncbut does not await it, instead accessesresult.Resultsynchronously.Step 2: Understand deadlock risk in orchestrators
AccessingResultblocks the thread and can cause deadlocks in async orchestrator functions.Final Answer:
It blocks the orchestrator causing a deadlock -> Option DQuick Check:
Use await, not .Result, to avoid deadlocks [OK]
- Using .Result instead of await
- Ignoring async method patterns
- Assuming synchronous access works fine
Task1, Task2, and Task3 in parallel and wait for all to finish before continuing. Which orchestrator pattern correctly achieves this in JavaScript Durable Functions?Solution
Step 1: Understand parallel execution in JavaScript
To run tasks in parallel, start them without awaiting immediately, collect promises, then await all together.Step 2: Analyze each option
const tasks = [ context.callActivity('Task1'), context.callActivity('Task2'), context.callActivity('Task3') ]; const results = await Promise.all(tasks); creates an array of promises and awaits them all withPromise.all, running tasks concurrently.Final Answer:
Use Promise.all with array of activity calls for parallel execution -> Option CQuick Check:
Parallel = start all, then await all [OK]
- Awaiting each task sequentially (Options A and C)
- Trying to add awaited results (Option D)
- Not collecting promises before awaiting
