Azure Functions Deep Dive: Triggers, Scaling, Durability and Production Operations
A book-style guide to how Azure Functions really works, from the host and the scale controller to hosting plans, reliable event handling, durable workflows, private networking and cost control.
Picture a modern post office with a very clever rule: it hires a clerk only when a letter arrives, the clerk handles exactly that letter, and when the mail stops the clerk goes home and stops being paid. If a thousand letters arrive at once, a thousand clerks appear within moments. Nobody builds the office, nobody schedules shifts, and nobody pays for empty desks. Azure Functions is that post office for your code. You write small pieces of logic, you tell the platform which event should wake each piece up, and the platform finds the machines, starts the code, scales it and cleans up afterward.
This guide goes well beyond a first function. We will open the machinery: what a trigger really does, how the scale controller decides to add workers, why messages can be processed twice, how orchestrations survive restarts, which hosting plan fits which workload, how private networking is wired, and which mistakes quietly damage production systems. Every new term is explained when it first appears, and every major idea comes with an everyday comparison.
1What Azure Functions Really Is
Azure Functions is an event-driven, serverless compute service that runs small units of code in response to things that happen.
The Simple Definition
Serverless does not mean there are no servers. It means you do not manage them. Azure Functions belongs to a family called Functions as a Service. You write a function, which is a small piece of code with one clear job. You attach a trigger, which is the event that starts it, such as an incoming web request, a new message in a queue, a timer that fires every night, or a file that lands in storage. The platform handles everything between the event and your code: finding capacity, starting a runtime, passing in the data and collecting the result.
It exists because many tasks are bursty and small. Sending a welcome email after sign-up, resizing an uploaded picture, processing one sensor reading, or calling a partner system every hour are all jobs that do not justify an always-running server. It is used for back-end APIs, data processing, scheduled jobs, integration glue between systems and long-running business workflows.
A regular server is a full-time employee who sits at a desk all day whether or not there is work. A function is a freelancer on call: when a job arrives, they appear, do exactly that job and leave. You pay for the jobs, not for the waiting.
What You Manage and What Microsoft Manages
Microsoft owns the datacenter, the host machines, the operating system, the runtime patching and the machinery that watches for events and adds workers. You own the code, the configuration, the identity and permissions, the data it touches, the network design and the cost decisions. The less you manage, the more you must understand the rules of the platform, because the platform makes decisions on your behalf, such as when to start and stop workers.
| Concern | Virtual Machine | App Service | Azure Functions |
|---|---|---|---|
| Operating system patching | You | Microsoft | Microsoft |
| Deciding how many instances run | You | You, or autoscale rules | Mostly the platform, within limits you set |
| Billing basis | Machine hours | Plan instance hours | Executions and resources used, or a baseline on some plans |
| Starts when | Always on | Always on | An event arrives |
| Best for | Anything custom | Web apps and APIs | Event-driven and bursty work |
Because workers appear and disappear, a function must never assume that the machine it ran on a minute ago still exists. Designing for disposable workers is the foundation of every good serverless system.
Where Functions Fit Among Azure Compute Choices
Functions sit at the small, event-driven end of the compute family. App Service suits full web applications. Container Apps suits containerized microservices. Kubernetes suits large platforms with deep orchestration needs. Logic Apps suits low-code business workflows with many ready-made connectors. Functions is the natural choice when the work is naturally triggered by an event and can be written as small pieces of code.
2Core Concepts and Vocabulary
Ten terms appear in nearly every conversation about Azure Functions. Learn them once and everything else becomes easier.
Function
One unit of code with a single entry point and exactly one trigger. It does one job well.
Function App
The deployment and scaling unit. It groups one or more functions that share settings, runtime, identity and, on most plans, the same instances.
Trigger
The event that starts a function. Every function has exactly one trigger, and the trigger carries the event data into the code.
Binding
A declarative connection to another service for input or output, so code reads or writes data without managing the connection by hand.
Host
The runtime process that listens for triggers, loads your code, manages bindings and reports health. It is the stage manager behind every function.
Language Worker
The process that runs your code in its own language. In the modern isolated model, your code runs in a separate worker from the host.
Scale Controller
The platform component that watches event sources and decides how many instances should run. You never see it, but it shapes your performance.
Instance
One running copy of your function app on one machine or container. Scaling out means more instances.
Host Settings File
A configuration file that tunes the host: batch sizes, concurrency, retry rules, logging levels and timeouts.
Extension Bundle
A versioned package of trigger and binding extensions that the host loads, so you do not install each connector yourself.
One Trigger, Many Bindings
The rule that every function has exactly one trigger but any number of bindings is important. The trigger answers the question: what wakes me up? The bindings answer: what else do I read from, and where do I write? A function can wake up on a queue message, read a record from a database through an input binding and write a result to blob storage through an output binding, all without opening a single connection in your own code.
A trigger is a doorbell. Only one doorbell rings your function awake. Bindings are the in-tray and out-tray on the desk: documents arrive in one, finished work goes into the other, and a courier handles the walking.
The Function App Is the Real Unit of Scale
Beginners often assume each function scales on its own. On most plans, it is the function app that scales: all functions inside an app are loaded on the same instances and share their memory and CPU. That is why putting a heavy, slow function and a tiny fast function in one app can make the tiny one suffer. One notable exception exists in the newest serverless plan, which scales certain groups of functions independently, as we will see in the scaling chapter.
Many people believe a function can have several triggers so it can respond to either a queue or a timer. It cannot. If two events should run the same logic, write two small functions that both call a shared piece of code.
3Architecture and Components
A look at the parts that sit between an event and your code.
The Big Picture
Behind a function app there are several cooperating parts. Event sources produce events. The scale controller monitors those sources and decides how many instances are needed. Each instance runs the host, which listens to the event sources, and a language worker, which runs your code. A storage account stores bookkeeping the host needs, such as coordination locks, timer state and, for some features, deployment content. Application Insights receives telemetry. Together these parts make the illusion that your code simply runs whenever needed.
flowchart TD
SRC["Event Sources: queues, topics, streams, HTTP, timers"] --> SC["Scale Controller"]
SC -->|"adds or removes"| I1["Instance 1"]
SC -->|"adds or removes"| I2["Instance 2"]
SC -->|"adds or removes"| I3["Instance N"]
I1 --> H1["Host and Language Worker"]
I2 --> H2["Host and Language Worker"]
I3 --> H3["Host and Language Worker"]
H1 --> ST["Storage Account for coordination and state"]
H2 --> ST
H3 --> ST
H1 --> AI["Application Insights"]
H2 --> AI
H3 --> AI
H1 --> OUT["Output targets: databases, storage, services"]
The Host: Stage Manager
The host is the process that knows how to listen. For a queue trigger, it opens a listener on the queue, pulls messages in batches and hands each message to your function. For a timer trigger, it keeps track of the schedule and fires at the right moments. For HTTP, it receives requests and routes them. The host also applies your retry rules, enforces timeouts, loads extensions and emits logs. Most surprising behaviors in Functions are really host behaviors.
The Isolated Worker Model
In the modern model, your code runs in its own process, separate from the host. This gives you control over dependencies and runtime versions, a standard programming model, and fewer clashes with the host’s own libraries. Newer plans support only this isolated model for .NET. The older in-process model, where your code loaded into the host’s own process, is being phased out, so new projects should choose isolated from the start.
The Storage Account: Shared Notebook
Most function apps need a storage account. The host uses it to coordinate between instances, for example deciding which instance owns a timer schedule or which instance processes a given partition. Some plans also keep your deployment package there. If this storage account is slow, throttled, blocked by firewall rules or deleted, the entire app misbehaves, so treat it as critical infrastructure and never share it blindly across unrelated apps.
Scale Controller
Watches event sources, measures backlog and rate, and tells the platform to add or remove instances.
Host
Listens to triggers, runs retries, enforces timeouts and reports logs.
Language Worker
Runs your code in its own process and talks to the host over a local channel.
Storage Account
Holds coordination data, and on some plans the app package and state for durable features.
Give each production function app its own storage account for the host. Sharing one account between many apps creates noisy neighbors and makes failures hard to trace.
Runtime Versions, Language Versions and Extension Bundles
Three version numbers shape every function app. The host runtime version decides which platform features exist. The language version, such as a specific release of .NET, Java, Node.js or Python, decides which libraries and syntax you may use. The extension bundle version decides which trigger and binding behaviors are available. Each moves on its own schedule, and old versions eventually lose support. Treat versions as part of your maintenance calendar: review them every quarter, test upgrades in a copy of the app, and never discover a retirement notice on the day it takes effect.
The newest serverless plan, for example, supports modern long-term releases of several languages and only the isolated model for .NET. An app written for an older model cannot simply be switched over, so build upgrade time into your plans rather than treating it as a surprise.
Pin the extension bundle to a version range you have tested. An unpinned bundle can change trigger behavior after a platform update, and tracing a subtle change in batching or checkpointing back to a bundle version is slow detective work.
4Internal Working: The Life of One Event
Following one message from birth to completion shows where time, failures and duplicates come from.
sequenceDiagram
participant P as Producer
participant Q as Queue or Stream
participant SC as Scale Controller
participant H as Host on Instance
participant F as Your Function
participant D as Downstream Service
P->>Q: Publish event
SC->>Q: Observe backlog and rate
SC->>H: Ensure enough instances
H->>Q: Pull batch of events
H->>F: Invoke with event data
F->>D: Do the work
D-->>F: Result
F-->>H: Success
H->>Q: Mark complete or checkpoint
The Event Is Born
Something publishes a message, uploads a file, or a clock ticks. The event waits in its source until a listener asks for it.
The Scale Controller Notices
It measures how many events are waiting and how fast they arrive, then decides whether more instances are needed. If the app is idle at zero instances, it starts one.
The Host Pulls a Batch
The host on an instance reads a group of events, not just one, to be efficient. The size of the group and how many run in parallel are settings you can tune.
Your Code Runs
The host passes the event into your function through the language worker. If the worker process is not yet started, a cold start adds delay.
Completion Is Recorded
If your function succeeds, the host deletes the queue message or advances the checkpoint. If it fails, the event becomes visible again or is retried.
Telemetry Flows Out
Duration, success, failures and custom logs are sent to Application Insights, so you can later explain what happened.
At-Least-Once Delivery: The Rule That Surprises Everyone
Most triggers promise at-least-once delivery. That means an event will certainly be given to your function, but in rare moments it may be given more than once. A worker can crash after your code finishes but before the completion is recorded. When the event becomes visible again, another worker processes it a second time. Exactly-once processing across distributed systems is extremely hard, so the platform chooses the safer promise: never lose, sometimes repeat.
Imagine a courier who must get a signature before leaving a parcel. If the courier drops the pen, they cannot prove delivery, so they deliver again to be safe. You might receive two parcels but you will never receive none. Your job is to make receiving the same parcel twice harmless.
Cold Starts
A cold start happens when an event arrives and no instance is ready. The platform must allocate capacity, start the host and the language worker, and load your code before it can run. Cold starts depend on language, package size, startup work and the hosting plan. They matter most for user-facing HTTP calls and matter little for background queue processing. Plans with always-ready or prewarmed instances reduce or remove them, at a baseline cost.
Doing heavy initialization, such as downloading large files or building big caches, at startup. Every cold start now pays that price, and scale-out becomes slow exactly when you need speed.
The HTTP Time Limit
Regardless of hosting plan, an HTTP-triggered function must respond within about 230 seconds because the load balancer cuts idle connections after that time. Any task that may run longer should return early with an acknowledgement and continue in the background, using a queue or a durable workflow, and let the client check status later.
Graceful Shutdown and Scale-in
When the platform scales in or updates an instance, it asks the host to stop. The host stops taking new events and gives running invocations a grace period to finish. On the newer plans this grace period is generous, around an hour for scale-in, but it is not unlimited. If your function is still running when time runs out, it is cut off, the event is not marked complete and it will be delivered again. This is one more reason to keep each invocation short, make long work resumable and treat every run as something that might be interrupted halfway.
Checkpointing is the classic solution. Break a large job into small slices, record progress after each slice and let the next invocation continue from the last recorded position. A job that can be interrupted at any moment and resumed later is naturally robust against restarts, deployments and scale-in events.
Reading a long book with a bookmark is checkpointing. If someone takes the book away halfway through, you lose only the current page, not the entire day of reading, because the bookmark tells you where to continue.
5Hosting Plans: Choosing Where Functions Run
The hosting plan decides your scale limits, cold-start behavior, networking options and bill. Choosing well is the biggest single decision.
The Five Options
| Plan | Scaling Style | Cold Start | Networking | Typical Fit |
|---|---|---|---|---|
| Flex Consumption | Event-driven, per-function groups, up to a high instance cap | Improved at zero, optional always-ready instances | Outbound VNet integration, inbound private endpoints | New serverless apps that need networking and fast scale |
| Premium | Event-driven with prewarmed workers | At least one warm instance | Full VNet integration, private endpoints | Steady traffic, low latency, long runs, predictable cost |
| Dedicated (App Service plan) | Manual or autoscale rules | None if Always On | Same as App Service | Reusing existing capacity, predictable bills |
| Container Apps | Event-driven, container-based | Scale to zero possible | Outbound integration, service endpoints | Teams standardizing on containers |
| Consumption (legacy) | Event-driven, scales to zero | Possible | None | Existing apps being migrated away |
Flex Consumption: The Recommended Serverless Plan
It is a Linux plan that keeps scale-to-zero and pay-for-use billing, and adds virtual network support, a choice of instance memory sizes of 512, 2,048 or 4,096 megabytes, faster scaling and per-function scaling. You can keep a chosen number of always-ready instances to avoid cold starts. It supports code deployment only, one function app per plan, and it has no deployment slots.
Premium: Warm, Strong and Predictable
Premium keeps at least one instance always warm and uses prewarmed workers to absorb spikes, so cold starts largely disappear. It offers larger instances, long execution times, full networking including private endpoints, deployment slots and many apps per plan. Billing follows the cores and memory of needed and prewarmed instances, which is the most predictable among the dynamic plans.
Dedicated: Borrowing App Service Capacity
Here functions run on an ordinary App Service plan. You pay standard plan rates, scale manually or with autoscale, and must enable Always On so the host does not unload. It makes sense when you already have spare capacity in a plan or need the plan features of an isolated environment.
Container Apps: Functions as Containers
Function apps can run as containers on the Container Apps platform, with event-driven scaling that can reach zero. This fits teams that already run everything as containers and want one platform.
Consumption: The Legacy Serverless Plan
The original serverless plan still exists for Windows, but Microsoft recommends Flex Consumption for new serverless apps. Per Microsoft’s notes, Linux apps on the version 3 runtime in this plan stopped running after 30 September 2026, and Linux Consumption as a whole retires on 30 September 2028 with no new features. Plan migrations early, because an app cannot be converted in place and must be recreated and redeployed.
Key Numbers Worth Remembering
Advantages of Serverless Plans
- Pay mostly for actual use
- Automatic scale from zero to very large
- No operating system maintenance
- Natural fit for event-driven designs
- Fast time to first working feature
Trade-offs to Accept
- Cold starts unless you pay for warm capacity
- Platform-defined limits on scale rate and timeouts
- Duplicate deliveries require careful design
- Harder local reproduction of production behavior
- Cost can surprise you under runaway loops
6Triggers and Bindings in Depth
Each trigger type has its own personality: how events arrive, how they are counted, how failures are handled and how scaling works.
The Common Triggers
HTTP
Runs when a web request arrives. Ideal for small APIs and webhooks. Must answer within the platform time limit, and cold starts are visible to callers.
Timer
Runs on a schedule. The host ensures only one instance runs a given schedule at a time, even when many instances exist.
Storage Queue
Simple, cheap message queue. Messages that fail repeatedly move to a separate poison queue after a set number of attempts.
Service Bus
Enterprise messaging with queues, topics, sessions, ordering options and dead-letter handling after repeated delivery failures.
Event Hubs
High-volume streams split into partitions. Processing uses checkpoints that record how far each partition has been read.
Event Grid
Push-style event routing from Azure services and custom sources. The platform delivers events to your function with retries.
Blob Storage
Runs when a file appears or changes. The recommended source mode uses Event Grid for fast, reliable notifications instead of slow scanning.
Cosmos DB Change Feed
Runs when documents change, using a lease container to remember progress and divide work between instances.
Choosing a trigger is like choosing how a restaurant learns about orders. A waiter shouting orders is HTTP: immediate, and the customer waits. A ticket rail is a queue: orders wait in line and cooks take them at their own pace. A giant conveyor belt with numbered lanes is Event Hubs: huge volume, with each lane watched by a single cook.
Input and Output Bindings
Bindings reduce plumbing. An input binding fetches a document, a blob or a row before your code runs. An output binding writes a message, a document or a file after your code finishes. This keeps functions short and makes them easy to test. The trade-off is that bindings hide connection behavior, so for high-throughput or very custom needs, many teams create and reuse a client object themselves, which brings us to a key rule below.
Reuse Clients, Do Not Recreate Them
Each invocation is short, but the process that runs it lives longer. Creating a new connection to a database or a web service for every call wastes time and can exhaust available connections and network ports. Create clients once when the process starts and reuse them across invocations. This single habit prevents a large share of production problems in serverless code.
| Trigger | Delivery Promise | Failure Handling | Scaling Limit Hint |
|---|---|---|---|
| HTTP | Request and response, caller may retry | Caller sees an error | Instances, concurrency and plan limits |
| Storage Queue | At least once | Poison queue after repeated failures | Queue backlog and batch settings |
| Service Bus | At least once, optional sessions | Dead-letter after maximum deliveries | Backlog, sessions and concurrency settings |
| Event Hubs | At least once with checkpoints | Retry policy or skip with care | Number of partitions |
| Timer | Single schedule owner | Missed runs can be caught up | Not applicable |
| Cosmos DB Change Feed | At least once | Retry or custom error path | Leases and physical partitions |
Expecting Event Hubs to scale to a thousand instances for a hub with four partitions. Partitions are the unit of parallelism, so the number of useful instances is bounded by them. Plan partition counts before traffic grows.
Ordering, Batching and Sessions
People often ask whether messages are processed in order. The honest answer depends on the trigger. Simple storage queues give no strict ordering promise. Service Bus can preserve order within a session, so all messages with the same session key are handled one after another by a single consumer, while different sessions are processed in parallel. Event Hubs preserve order within a partition, so choosing a good partition key, such as a customer or device identifier, keeps related events in sequence. If your logic truly needs global ordering, expect to give up parallelism and reconsider whether the design can tolerate slight reordering instead.
Batching is the other side of this coin. Many triggers can hand your function several events at once. Batches reduce overhead and let you write to a database in bulk, but one bad event inside a batch can cause the whole batch to be retried unless you handle partial failures deliberately. Decide per workload whether you prefer small batches with simple error handling or large batches with careful per-item tracking.
Session Key Design
Choose a session or partition key that groups events needing order, such as one order or one device, while keeping the number of distinct keys large. A single hot key serializes everything behind it and becomes a bottleneck.
Partial Batch Failure
When processing a batch, record which items succeeded so a retry does not repeat them. Combined with idempotent writes, this keeps large batches both fast and safe.
7Scaling Deep Dive
Scaling in Functions is automatic but not magical. Understanding its inputs lets you shape it instead of being surprised by it.
What the Scale Controller Looks At
The scale controller uses different signals for different triggers. For queues and topics, it looks at backlog size and the age of the oldest waiting message. For streams, it looks at how far behind each partition is. For HTTP, it looks at incoming request rate and the number of concurrent requests per instance. When backlog grows, it adds instances. When work falls, it removes them gradually and, on scale-to-zero plans, eventually to none.
Instances Versus Concurrency
Two knobs control how much work happens at once. Instances are the number of machines. Concurrency is how many invocations each instance runs in parallel. A single instance can often run many invocations at once, particularly when the work waits on network calls. Raising concurrency can reduce the number of instances needed for the same load, but it raises memory pressure and the load on downstream services. Newer plans offer per-function and per-trigger concurrency controls, and many triggers support target-based scaling, which computes the needed instances directly from the backlog and the target number of events each instance should handle.
flowchart LR
B["Backlog or Request Rate"] --> SC["Scale Controller"]
SC --> N["Needed Instances"]
N --> L{"Within plan limits and quota?"}
L -->|"yes"| ADD["Add instances"]
L -->|"no"| CAP["Capped by maximum or regional quota"]
ADD --> C["Each instance runs many invocations"]
C --> D["Downstream database or API"]
D -.-> PR["Pressure on dependencies"]
PR -.-> LIM["Set maximum instances and concurrency to protect them"]
Per-Function Scaling on Flex Consumption
On the newest serverless plan, functions are grouped by trigger type for scaling. All HTTP-triggered functions scale together on shared instances, all blob-triggered functions that use Event Grid scale together, and durable function triggers scale together. Every other function scales on its own instances. This means a slow queue processor cannot starve a fast HTTP endpoint inside the same app, which was a real limitation in older plans. The maximum instance count applies per group, and always-ready instances are counted separately from that maximum.
| Plan | Approximate Instance Ceiling | Notes |
|---|---|---|
| Flex Consumption | Configurable up to 1,000 per function group on Linux | Also limited by a regional core quota, which can be raised by request |
| Premium | About 100 on Windows, and roughly 20 to 100 on Linux depending on region | At least one warm instance and prewarmed workers |
| Dedicated | Tens of instances, higher in an isolated environment | Manual or autoscale |
| Consumption legacy | About 200 on Windows | Scales to zero |
Scale-Out Speed Is Not Instant
Scale-out is quick when the app is small and becomes more gradual as instance counts grow. Occasional throttling during scale-out is normal and the platform retries. If your workload has a sharp spike, such as a marketing e-mail that triggers a flood of calls in one minute, pre-scale with always-ready instances, use a queue to smooth the spike, or both.
Scaling is like opening extra checkout lanes in a supermarket. Opening the second and third lane is quick. Opening the fiftieth requires finding staff, registers and floor space, so it takes longer. A queue of waiting shoppers, the backlog, is a perfectly reasonable way to absorb a short rush.
Protecting What Sits Behind the Function
The cruel truth of serverless is that the function tier scales better than the database behind it. A thousand instances can open a thousand times more connections than a modest database accepts. Always set a maximum instance count, tune concurrency, use connection pooling, and put a queue between fast producers and slow dependencies. The platform will happily scale until something downstream breaks, unless you set boundaries.
Problem
A function triggered by a large queue writes directly to a small relational database with no limit on scale.
Why It’s Harmful
The queue backlog causes rapid scale-out, hundreds of simultaneous connections overwhelm the database, errors spike, messages retry and the load gets worse.
Correct Approach
Cap the maximum instances and per-instance concurrency to what the database can bear, pool connections and batch writes where possible.
A Practical Method for Tuning Scale
Find the Real Bottleneck
Use a realistic load test and watch every part: function duration, instance counts, database connections and downstream error rates. Tune the weakest link first.
Set Boundaries
Choose a maximum instance count and per-instance concurrency that the slowest dependency can survive, with a safety margin.
Choose the Memory Size
On plans with selectable instance sizes, test a small, a medium and a large option. A larger instance may finish faster and allow more concurrency, so the total cost can even drop.
Decide on Warm Capacity
Measure cold start delay against your latency goal. Add always-ready or prewarmed capacity only for the functions that truly need it.
Repeat After Every Big Change
New code, new dependencies or new traffic patterns change the numbers. Treat tuning as a recurring habit, not a one-time task.
Scale is also limited by regional core quotas on some plans. Before a big launch, confirm the quota for your subscription and region and request an increase early, because approvals take longer than a sudden spike gives you.
8Reliability: Retries, Poison Messages and Idempotency
Reliable serverless code assumes that every step may fail and every message may arrive twice.
Idempotency: Making Repeats Harmless
An operation is idempotent when doing it twice has the same effect as doing it once. Setting a status to shipped is idempotent. Adding one to a counter is not. Because duplicates can occur, every function that changes the world should be designed so a repeat is harmless. Common techniques include using a unique event identifier and recording that it was processed, writing with upsert operations instead of blind inserts, and using conditional updates that check the current state first.
A light switch labeled on is idempotent: pressing it ten times leaves the light on. A button labeled toggle is not: ten presses leave the light in a state nobody can predict. Build your functions like the first switch.
Retries
There are several layers of retry. The trigger itself may redeliver a message when a function fails. The host may apply a retry policy, with either a fixed delay or an exponential delay between attempts, for supported trigger types such as timers, Event Hubs and Cosmos DB. Your own code may retry calls to downstream services. When these layers combine, a single failure can multiply into many attempts, so keep an eye on the total. Always use delays that grow, limit the number of attempts and add a bit of randomness to avoid thundering herds.
Poison Messages and Dead-Lettering
A poison message is one that fails every time, perhaps because its content is malformed. Without a safety net, it would be retried forever and could block other messages. Messaging services handle this by moving the message aside after a maximum number of delivery attempts: Storage queues use a poison queue, and Service Bus uses a dead-letter queue. The critical operational step is to monitor those side queues, alert when they receive anything and have a procedure to inspect, fix and replay messages.
First Failure
The function throws an error. The message becomes visible again after a short delay, and the delivery count increases.
Repeated Retries
The same message is delivered several more times. Each failure is logged with the event identifier.
Message Moved Aside
After the maximum delivery count, the message goes to the poison or dead-letter location, freeing the main flow.
Alert and Inspect
An alert tells the team. Someone examines the content to find the cause: bad data, a bug or a broken dependency.
Fix and Replay
After the fix, messages are replayed safely, relying on idempotent processing to avoid double effects.
Streams Need Special Care
With a stream such as Event Hubs, one bad event inside a partition can block everything behind it if you retry forever, because the partition must be processed in order. You must decide deliberately: retry a limited number of times, then move the failed event to a side store for later analysis and continue. Silently skipping events loses data, and retrying forever stalls the partition, so pick a documented policy.
Add the event identifier to every log line. When a duplicate or a failure occurs, you can follow that one event through every function and retry in seconds.
Problem
A function catches every error, logs it and returns normally so the message is never retried.
Why It’s Harmful
The platform believes the work succeeded. Failed events vanish silently, with no retry, no dead-letter record and no alert.
Correct Approach
Let unexpected errors surface so the trigger can retry or dead-letter. Handle only errors you can truly recover from, and record the rest explicitly.
9Durable Functions and Workflow Patterns
When work has many steps, waits, branches or human approvals, plain functions are not enough. Durable Functions adds memory to serverless code.
What Durable Functions Adds
Durable Functions is an extension that lets you write stateful workflows as ordinary code. An orchestrator function describes the steps, and activity functions do the actual work. The framework records progress in a durable store, so the workflow can pause for days, survive restarts and resume exactly where it stopped. This means you can express a multi-step business process in a readable sequence instead of scattering state across queues and tables.
A normal function is a person with no memory who must be told everything each time. A durable orchestration is a project manager with a notebook. After every completed task, they write it down, so if they fall asleep or are replaced, the next manager reads the notebook and carries on.
The Main Patterns
Function Chaining
Run steps in a fixed order, passing each result to the next. Good for pipelines like validate, charge, ship, notify.
Fan-out and Fan-in
Start many activities in parallel and wait for all to finish, then combine results. Good for processing many files or items at once.
Async HTTP API
A client starts a long job, gets a status address immediately and polls until completion, avoiding the HTTP time limit.
Monitor
Repeatedly check a condition using durable timers until it is true or a deadline passes.
Human Interaction
Pause until a person approves or a timeout expires, without holding any compute while waiting.
Entities
Small durable objects that hold state, such as a counter or a shopping cart, updated safely by many callers.
flowchart TD
C["Client request"] --> S["Starter function"]
S --> O["Orchestrator"]
O --> A1["Activity: validate order"]
A1 --> O
O --> F{"Fan out"}
F --> A2["Activity: reserve item 1"]
F --> A3["Activity: reserve item 2"]
F --> A4["Activity: reserve item 3"]
A2 --> J["Fan in and combine"]
A3 --> J
A4 --> J
J --> H["Wait for human approval or timeout"]
H --> A5["Activity: ship and notify"]
S -.-> ST["Status address returned immediately"]
Replay: The Rule That Shapes Orchestrator Code
Durable orchestrations achieve their magic by replay. Each time an orchestrator needs to continue, the framework runs the orchestrator code again from the beginning, but returns the recorded results for steps that already completed instead of doing them again. The code reaches its previous position quickly and moves on. Because of this, orchestrator code must be deterministic: given the same history, it must make the same decisions every time.
| Do in an Orchestrator | Never in an Orchestrator |
|---|---|
| Call activity functions for any real work | Call a database, a web service or a file system directly |
| Use the framework’s own current time value | Read the machine clock, random numbers or new unique identifiers |
| Use durable timers to wait | Use blocking sleeps or unmanaged threads |
| Restart with fresh state for endless loops | Loop forever and let history grow without bound |
Reading the current time or generating a random value directly inside an orchestrator. On replay the value differs, the history no longer matches and the orchestration fails with confusing errors. Use the context-provided versions or move that logic into an activity.
Storage Providers and Limits
Orchestration state lives in a storage provider. On the newest serverless plan, supported choices are Azure Storage and the Durable Task Scheduler. Each choice has different throughput, cost and monitoring characteristics, so test with realistic volumes. Keep activity inputs and outputs small, because large payloads are stored in the history and slow replay. Store large data in blob storage and pass only references.
Sub-Orchestrations and Versioning
Large workflows are easier to understand when broken into smaller orchestrations that call each other, much like functions calling functions. A parent orchestration can start child orchestrations, wait for them and combine their results, and each child keeps its own history, which keeps individual histories small. This also helps when a rule needs different retry behavior for different parts of a process.
Versioning deserves planning before the first production release. Running orchestrations replay against the code that is deployed, so changing the order or number of steps in code can break orchestrations that were started before the change. Common strategies include letting old instances finish before releasing breaking changes, creating a new orchestration name for the new logic while the old one drains, and carrying a version number in the input so the code can branch deliberately. Choose one strategy and document it for the whole team.
Changing orchestrator code while orchestrations are running is like rewriting a recipe while a cook is halfway through following the old one. The cook, who is reading from a notebook of steps already done, suddenly finds that step four no longer exists.
Timers, Timeouts and Escalation
Durable timers make waiting free: while a workflow waits for an approval, no compute is running and no cost accrues. Combine a timer with an external event to build a deadline, so that if nobody approves within two days, the orchestration escalates, reminds or cancels automatically. Always cancel timers that are no longer needed, because an orphaned timer can keep an orchestration alive longer than expected.
10Networking Deep Dive
Functions often need to reach private databases and sometimes need to be reachable only privately. Inbound and outbound are different problems with different tools.
Inbound and Outbound
Access Restrictions
Allow or deny callers by IP range or subnet for the public address, evaluated before your code runs.
Private Endpoint
Gives the function app a private address in your network so public access can be switched off. Supported on Flex Consumption, Premium and Dedicated plans.
Virtual Network Integration
Lets your function reach private databases, vaults and APIs through a delegated subnet.
NAT Gateway or Firewall
Gives a stable outbound address that partners can allow, and a place to inspect and log egress traffic.
Plan-Specific Facts
On Flex Consumption, outbound integration uses a subnet delegated to the Microsoft.App environments service, and the Microsoft.App resource provider must be registered in the subscription. Hybrid Connections, the relay-based way to reach a single on-premises host, are not available on that plan. On Premium, you also get the older delegation style and Hybrid Connections on Windows. The legacy Consumption plan has no virtual network support at all, which is a major reason teams migrate away from it. Container Apps supports outbound integration and service endpoints but not private endpoints for the function app itself.
A private endpoint is a private door cut into your company building that employees can use from inside the corridor, letting you brick up the street entrance. Virtual network integration is the staff tunnel your couriers use to reach the secure warehouse across the road.
flowchart TD
U["Internal caller or API gateway"] --> PE["Private endpoint subnet"]
PE --> FA["Function App"]
FA --> INT["Integration subnet"]
INT --> SQL["Private database"]
INT --> KV["Key Vault with public access off"]
INT --> SA["Storage account with private endpoints"]
INT --> NAT["NAT gateway with fixed address"]
NAT --> EXT["Partner API with allow list"]
DNS["Private DNS zones"] -.-> PE
DNS -.-> SA
Do Not Forget the Host Storage Account
If you lock the host storage account behind a firewall or private endpoints, the function app must be able to reach it through the integration subnet, with correct private DNS for every storage service the host uses, such as blob, queue and table. Many first attempts at private function apps fail because the host cannot reach its own storage, and the symptom is an app that will not start. Test connectivity of the storage account before locking down the rest.
Private endpoint traffic bypasses IP access restriction rules. Control it with subnet security groups, and make sure private DNS resolves your app name to the private address from inside the network.
Subnet Planning
Integration subnets must be large enough for peak instance counts plus room for platform updates. On plans that can scale to hundreds of instances, a small subnet can stop scale-out in confusing ways. Plan address space early, avoid ranges that overlap with on-premises networks, and keep private endpoint subnets separate from integration subnets.
11Configuration, Identity and Secrets
Good function apps keep code, settings and secrets apart, and avoid passwords wherever an identity can do the job.
Application Settings
Application settings are named values the platform injects into your function app as environment variables. They let the same code behave differently in development, staging and production without rebuilding. They are encrypted at rest and can be changed without redeploying, although a change restarts the app. Many trigger and binding settings, such as the name of a queue connection, are also read from application settings, so naming discipline pays off.
Identity-Based Connections
Historically, connections to storage, messaging and databases used connection strings that contained secret keys. A better approach is identity-based connections. The function app has a managed identity, an account in Microsoft Entra ID that Azure creates and rotates for you. You grant that identity permission on the target service, for example the right to receive messages from a Service Bus queue, and then configure the connection by pointing to the service address rather than supplying a secret. Many core triggers and bindings, including storage, Service Bus, Event Hubs and Cosmos DB, support this model, as does the host’s own storage account connection.
A connection string is a house key copied and handed to every worker. An identity-based connection is a staff badge: the building knows who each person is, you can cancel one badge without changing any locks, and nobody walks around with a master key.
Key Vault References
Some secrets cannot be replaced by identity, such as a third-party API key. Store those in Azure Key Vault and place a reference in the application setting. At runtime, the platform fetches the real value using the app’s managed identity. The secret never appears in your repository, your pipeline or the settings screen, and rotation becomes a vault task.
flowchart LR
FA["Function App"] -->|"asks for token"| MI["Managed identity endpoint"]
MI --> ENTRA["Microsoft Entra ID"]
ENTRA -->|"short lived token"| FA
FA -->|"token"| SB["Service Bus"]
FA -->|"token"| COS["Cosmos DB"]
FA -->|"token"| ST["Storage"]
FA -->|"vault reference"| KV["Key Vault"]
System-Assigned or User-Assigned
A system-assigned identity is created with the app and deleted with it. A user-assigned identity is a standalone resource that can be shared by several apps or recreated alongside a replacement app. User-assigned identities are handy when you want permissions to survive redeployment of an app or share one identity across a set of closely related apps, but remember that a shared identity widens the blast radius if one app is compromised.
Least Privilege in Practice
Give the identity only the narrowest role at the narrowest scope. A function that only reads from one queue should have a receiver role on that queue, not an owner role on the namespace. Separate identities per environment prevent a bug in staging from touching production data.
Problem
Connection strings with full-access keys are pasted into application settings, source control or shared documents.
Why It’s Harmful
Anyone who can read configuration or the repository can take over the data store, and rotation requires coordinated changes across apps.
Correct Approach
Use identity-based connections for Azure services and Key Vault references for third-party secrets, with narrow permissions.
12Security: Defense in Depth
No single setting secures a function app. Layers do.
Function Keys Are Not User Authentication
HTTP functions have an authorization level. Anonymous allows anyone. The function level requires a key, and the admin level requires a master key. Keys are shared secrets that identify an application caller, not a person, and they cannot express who a user is or what that user may do. For real user sign-in, use the platform’s built-in authentication with Microsoft Entra ID or another provider, or place the app behind an API gateway that validates tokens. Authorization decisions, such as roles and data ownership, still belong in your code.
A function key is like a shared office door code: everyone who knows it gets in, and you cannot tell who entered. A signed-in identity is a personal badge that records exactly who walked through and which rooms they may open.
The Layers
Edge Protection
Put an API gateway or a web application firewall in front of public HTTP functions for token validation, throttling and attack filtering.
Transport Security
Require HTTPS only and a modern minimum TLS version. Disable plain FTP style deployment endpoints you do not use.
Identity and Access
Managed identities for outbound calls, Entra sign-in for users and role-based access control for who may manage the app.
Private Networking
Private endpoints for the app where suitable, and private access for databases, vaults and storage.
Input Validation
Treat every event as untrusted. Validate shape, size and content, even from internal queues.
Detection
Central logs, alerts on unusual failure or authentication patterns and Defender for cloud protections.
Supply Chain and Code
Serverless code often pulls in many third-party packages. Pin versions, scan dependencies for known vulnerabilities, keep runtime versions current and rebuild regularly. A forgotten library with a known flaw is as dangerous inside a function as anywhere else, and many small functions can multiply the number of places where an outdated package hides.
Cross-Origin and Browser Concerns
If a browser application calls your HTTP functions directly, configure allowed origins carefully and avoid wildcards for APIs that return user data. Send security headers from the gateway or from your code to reduce browser-based attacks.
Believing that an internal queue only carries trusted data. Messages are written by other systems, which can have bugs or be compromised. A function that blindly trusts its input can be turned into a weapon by one bad message.
Problem
A public function protected only by a function key is embedded in a mobile app or a web page.
Why It’s Harmful
The key can be extracted by anyone who inspects the app, and it identifies no user. A leak forces a key rotation affecting every caller.
Correct Approach
Authenticate users with tokens, validate them at a gateway or built-in authentication, and use keys only for server-to-server callers.
Secrets Rotation and Key Hygiene
Even when most access uses identities, some keys still exist: function keys for server callers, signing keys for webhooks and third-party API credentials. Give each caller its own key instead of sharing one, so a single leak can be revoked without breaking everyone. Rotate keys on a schedule and after every staff change that touched them, store them only in a vault and record which system uses which key. Review access logs regularly for keys that are no longer used, and remove them, because every forgotten credential is an open door waiting for the wrong visitor.
Treat webhook endpoints with extra suspicion, since they are reachable by design. Verify signatures supplied by the sender, reject requests with old timestamps to stop replay attacks, and limit the size and rate of incoming payloads before any expensive work begins.
13Deployment and Release Engineering
Shipping new function versions safely is trickier than shipping a web app, because background triggers keep running during the release.
How Code Reaches a Function App
Most teams deploy through a pipeline that builds, tests and publishes one immutable package. The app then runs directly from that package. On Flex Consumption, the package is a compressed archive uploaded to a blob container, and the app runs from it at startup, with the default being the host storage account, though you may choose a different account and authentication method. Container-based apps deploy an image from a registry instead.
Slots and Their Limits
Deployment slots, which let you stage a new version and swap it into production, are available on Premium, with a small number allowed, and on Dedicated plans, with more on higher tiers. The legacy Consumption plan offers only a couple, and the Flex Consumption plan has no slots at all, though a zero-downtime rolling update option is in public preview. When slots are not available, the usual approach is to deploy a complete second function app and switch traffic at a gateway, or to rely on rolling updates and idempotent processing.
The Hidden Danger: Triggers in Every Slot
A staging slot is a real, running function app. If its queue trigger points at the same queue as production, it will compete with production for messages and process live work with unfinished code. The same is true for timers and change feed triggers. Always give slots their own trigger sources through slot-specific settings, or disable the triggers in staging until the swap. Forgetting this is one of the most expensive mistakes in serverless releases.
flowchart LR
CI["Pipeline builds one package"] --> STG["Staging slot or second app"]
STG --> T["Smoke tests with test queues"]
T --> SW["Swap or traffic switch"]
SW --> PROD["Production"]
PROD --> OLD["Previous version kept for rollback"]
STG -.-> RISK["Risk: staging triggers may consume production events"]
RISK -.-> FIX["Fix: separate trigger sources or disable triggers until swap"]
Build Once
Compile, test and produce one package. The same artifact moves through every environment.
Deploy Away from Live Events
Release to a staging target whose triggers point at test sources or are disabled.
Verify
Send test events and confirm the full path works, including bindings, identities and network access.
Switch and Watch
Move to production, then watch error rates, backlog age and dead-letter counts closely.
Keep a Way Back
Hold the previous version ready for rollback, and make sure messages processed by the new version remain valid for the old one.
Versioning Messages
During a release, old and new instances run side by side for a while, and queues may contain messages written by either version. Keep message formats backward and forward compatible: add fields instead of renaming them, ignore unknown fields and include a version number. This is the same expand and contract discipline used for databases.
Infrastructure as Code
Define function apps, plans, identities, networking and monitoring in repeatable templates. Because serverless systems consist of many small connected resources, manual configuration drifts quickly, and a template is the only reliable way to recreate an environment or review a change before it happens.
Tag every deployment with a version in telemetry. When errors start, you can instantly tell whether they began with a specific release.
Testing Strategy for Functions
Functions are easy to run locally with the command-line tools and a storage emulator, which makes the inner loop fast. Good teams build three layers of tests. Unit tests check the business logic with no cloud dependencies, by keeping the function body thin and moving real logic into ordinary classes or modules. Integration tests run the function against real or emulated queues, storage and databases to prove that bindings, serialization and settings work. End-to-end tests run in a real cloud environment with real identities and networking, because many problems, such as permissions, private DNS and quotas, appear only there.
Unit Tests
Fast and numerous. Test logic, edge cases and idempotency rules without touching the cloud.
Integration Tests
Prove that triggers, bindings and serialization behave correctly against real or emulated services.
Cloud Tests
Verify identities, networking, quotas and monitoring in a production-like environment before release.
Failure Drills
Deliberately send bad messages, stop a dependency and duplicate events to prove that retries and dead-lettering work.
Testing only the happy path with a single message. Real systems see duplicates, malformed input, slow dependencies and out-of-order events. If your tests never produce those, production will.
14Monitoring, Logging and Diagnostics
Event-driven systems fail quietly. Without good signals, a stuck queue can go unnoticed for days.
Three Kinds of Signals
Metrics
Numbers over time such as execution counts, failures, duration and instance counts. Cheap and ideal for alerts.
Logs
Records of each invocation and your own messages, searchable by event identifier.
Traces
The path of one event across functions and services, showing where time was spent.
Metrics are the warning lights on a dashboard, logs are the maintenance diary and traces are the footage of one specific journey. A good mechanic wants all three.
Application Insights
Application Insights is the standard telemetry service for Functions. It records each invocation, its duration and result, outgoing dependency calls, exceptions and custom events. It also draws a map of how functions and services call each other. Enable it from day one, because history is most valuable on the day something breaks.
Sampling and Cost of Logs
Serverless apps can produce enormous numbers of invocations, and logging each one in detail can cost more than the compute. Sampling keeps a representative portion of telemetry while preserving counts. Tune log levels in the host settings so that noisy informational lines are reduced in production, but never sample away errors. A common mistake is to switch off logging to save money and then be blind during an outage.
What to Watch
| Signal | Why It Matters |
|---|---|
| Failure rate per function | Shows which logic is breaking and since when |
| Execution duration percentiles | Reveals slow dependencies and cold start impact |
| Backlog size and age of oldest message | The best sign that processing is falling behind or stuck |
| Poison and dead-letter counts | Any non-zero value deserves attention |
| Instance count over time | Explains cost and scaling behavior |
| Throttling and dependency errors | Shows when downstream services are the bottleneck |
| Host startup errors | Catches broken settings, storage access or network issues quickly |
Urgent Page
Failure rate high, backlog age rising fast or the app failing to start.
Working-hours Ticket
Slowly rising duration, growing dead-letter queue or rising cost per execution.
Capacity Warning
Scale approaching the maximum or the regional quota, giving time to raise limits.
Security Signal
Unusual authentication failures, key usage spikes or permission changes.
Tracing Across Functions
When one event passes through several functions and queues, pass a correlation identifier along with each message and write it in every log line. Without it, reconstructing the journey of a single order across five functions feels like following footprints through a rainstorm.
Alerting only on exceptions. A stuck consumer, an empty trigger or a disabled function raises no exception at all. Always alert on the age of the oldest unprocessed message and on the absence of expected activity.
15Design Patterns and Anti-patterns
Patterns are proven shapes. Anti-patterns are the shapes that keep causing incidents.
Helpful Patterns
Queue-Based Load Leveling
Put a queue between a fast producer and a slower consumer. The queue absorbs spikes, and the function drains it at a safe rate.
Event-Driven Fan-out
Publish one event to a topic or event grid and let many independent functions react to it, each doing one job. New behavior is added by adding a subscriber, not by changing the producer.
Claim Check
When messages carry large data, store the data in blob storage and send only a small reference. Queues stay fast and cheap.
Saga with Compensation
For business processes spanning several services, run each step as a local transaction and define an undo action for each. If a later step fails, run the undo actions in reverse. Durable orchestrations express this clearly.
Transactional Outbox
Write the data change and the outgoing event record in the same database transaction, then let a function publish the event. This avoids saving data without announcing it, or announcing it without saving.
Strangler Migration
Move pieces of a legacy application into functions gradually, routing one capability at a time until the old system can be retired.
Circuit Breaker and Backoff
When a dependency fails, stop hammering it. Retry with growing delays and let a breaker fail fast until the dependency recovers.
Anti-patterns to Avoid
Problem
One giant function does validation, business rules, database writes, notification and reporting in a single run.
Why It’s Harmful
It is slow, hard to test, hard to retry safely and impossible to scale its parts independently. A failure late in the run repeats all earlier work.
Correct Approach
Split into small functions connected by queues or an orchestration, each idempotent and focused on one job.
Problem
Functions call each other over HTTP and wait for answers in long synchronous chains.
Why It’s Harmful
You pay for every function to sit waiting, latency adds up, timeouts cascade and one slow step blocks the whole chain.
Correct Approach
Use queues or events between steps, or a durable orchestration for coordinated flows.
Problem
A function writes to the same container or queue that triggers it, creating a loop.
Why It’s Harmful
Each run triggers another run. The loop scales automatically, and the bill grows with it until someone notices.
Correct Approach
Write results to a different location, add a clear exit condition, set a maximum instance count and alert on unusual execution counts.
16Cost Optimization and Governance
Serverless bills follow behavior. Understanding what is billed lets you shape the bill instead of fearing it.
How Each Plan Bills
| Plan | What You Pay For | Surprise to Watch |
|---|---|---|
| Flex Consumption, on demand | Memory-time while instances execute, plus the number of executions, with free grants | Larger instance memory sizes cost more per second of execution |
| Flex Consumption, always ready | A baseline for always-ready instances, plus memory used while executing and executions | Always-ready instances bill even when idle and have no free grants |
| Premium | Core seconds and memory for needed and prewarmed instances | The always-warm baseline is a constant minimum cost |
| Dedicated | Ordinary App Service plan rates | You pay whether or not functions run |
| Container Apps | Per the Container Apps plan type | Minimum replicas above zero bring a baseline |
On-demand serverless is paying a taxi per ride. Always-ready instances are paying a driver a small retainer to wait outside your door. Premium is leasing a car with a driver on standby. Dedicated is owning the car. Each is cheapest at a different level of usage.
Where the Money Really Goes
Compute is only one part. Executions that wait on slow dependencies are billed for their waiting time. Detailed logging can cost as much as compute. Storage transactions from the host and from durable workflows add up quickly under heavy load. Data leaving a region, private endpoints, firewalls and gateways also add charges. A runaway loop is the single most expensive failure, because automatic scaling turns a small bug into a large bill.
Levers to Spend Less
Make Functions Faster
Reduce waiting on dependencies, reuse clients and run calls concurrently. Shorter executions cost less on every consumption-style plan.
Right-size Memory
Choose the instance size that fits real memory use. Too big wastes money, too small causes crashes and slowdowns.
Batch Where Possible
Process several events per invocation when the trigger supports it, reducing per-execution overhead and downstream round trips.
Cap the Scale
Set maximum instances to protect both the bill and the dependencies behind the function.
Use Warm Capacity Sparingly
Keep always-ready instances only for latency-critical functions, not for everything.
Tune Telemetry
Sample routine traces and reduce chatty log levels, while always keeping errors.
The Break-even Question
Serverless on-demand is cheapest for spiky or low traffic. As traffic becomes steady and high, a baseline plan such as Premium or Dedicated can become cheaper per request, because you pay for capacity that is always busy. Estimate your monthly execution count, average duration and memory, compare plans in the pricing calculator and revisit the decision as traffic grows.
Governance Basics
Use Azure Policy to require HTTPS only, restrict public access where it should not exist and enforce minimum TLS. Tag every function app with owner, product and cost center. Use role-based access control so only a few people can create expensive plans or change networking. Set budgets and anomaly alerts so surprise spend becomes a notification, not a month-end shock.
Add an alert on the number of executions per hour. A sudden tenfold jump is almost always a loop, a retry storm or an attack, and finding it in minutes can save a very large bill.
A Worked Cost Thought Experiment
Imagine a function that runs two million times a month, each run taking half a second on a mid-sized instance. On an on-demand serverless plan, you pay mainly for the memory-time of those one million seconds plus the per-execution charge, and a monthly free grant offsets a part of it. Now imagine the same function receives a steady stream all day, so instances would be busy almost continuously. A plan with a fixed baseline, where you pay for capacity whether or not it is used, may now cost less because the capacity is always working. The point is not the exact numbers, which change with region and time, but the method: estimate executions, duration and memory, price both models in the calculator and compare at your real traffic level.
Remember to include the supporting costs in the comparison. Logging volume, storage transactions from the host and durable workflows, private endpoints, gateways and outbound data transfer can together equal or exceed the compute charge, especially for tiny, very frequent functions.
17Real-World Examples and Choosing Alternatives
Patterns become clear when you see how different organizations shape the same service for different goals.
Industry Scenarios
Retail Order Processing
An online shop receives orders through an API, places them on a topic and lets separate functions handle payment, stock, shipping labels and customer messages. A durable orchestration coordinates the steps and compensates if payment fails after stock is reserved. Peak seasons scale automatically while idle nights cost almost nothing.
Internet of Things Telemetry
Thousands of devices send readings to an event stream. Functions validate, enrich and write data to a store, and raise alerts on abnormal values. Partition counts are planned in advance because they cap parallelism, and failed events go to a side store for review.
Document and Image Processing
When a file lands in storage, an event wakes a function that resizes, scans or extracts text and writes the result elsewhere. Fan-out processes batches in parallel, and the claim check pattern keeps messages small.
Financial Reconciliation
A timer starts a nightly orchestration that pulls statements from several systems, compares them in parallel and waits for human approval of exceptions. Private endpoints, managed identities and detailed audit logs satisfy strict controls.
Integration Glue
Functions connect software-as-a-service tools: when a customer record changes in one system, a function updates others, applying retries and idempotent writes so repeated events do no harm.
Functions Versus Other Choices
| Need | Best Fit | Why |
|---|---|---|
| Small event-driven code with bursty load | Azure Functions | Triggers, bindings and automatic scale with low idle cost |
| A full web application with pages and sessions | App Service | Built for always-on web workloads with slots and steady capacity |
| Low-code workflows with many connectors | Logic Apps | Visual designer and ready-made connectors for business processes |
| Containerized microservices with event scaling | Container Apps | Container-native platform that can scale to zero |
| Large platforms needing deep orchestration | Kubernetes Service | Maximum control with maximum operational effort |
| Long workflows in code with state | Functions with Durable Functions | Stateful workflows written as ordinary code |
Forcing a large, chatty web application into hundreds of tiny functions because serverless is fashionable. Pick the platform that matches the shape of the work, not the trend of the year.
Moving From Older Plans or Servers
Many teams migrate workers that run on virtual machines, scheduled tasks or the legacy Consumption plan. A calm migration assesses triggers, dependencies and network needs, fixes blockers such as local file state, builds the target with templates, runs old and new side by side with separate trigger sources, switches gradually and retires the old system only after a full business cycle. Because an app cannot be converted in place between some plans, treat each move as a fresh deployment with a data and trigger cutover plan.
18Best Practices and Common Mistakes
A compact checklist for design reviews.
Do These
- Make every function idempotent and safe to repeat
- Reuse clients and connections across invocations
- Keep functions small, focused and quick
- Use queues or durable orchestrations between steps
- Set maximum instances and tune concurrency for dependencies
- Use managed identities and Key Vault references
- Monitor backlog age, dead-letter counts and failure rates
- Give each app its own host storage account
- Keep message formats compatible across versions
Avoid These
- Swallowing errors so the platform thinks work succeeded
- Long synchronous chains of HTTP calls between functions
- Doing heavy work during startup
- Running tasks near or beyond the HTTP time limit inside requests
- Writing to the same place that triggers the function
- Relying on function keys for user security
- Letting staging triggers consume production events
- Non-deterministic code inside orchestrators
- Choosing a plan only by headline price
A Maturity Ladder
Working
Functions run on a suitable plan with Application Insights enabled and basic alerts on failures.
Reliable
Idempotent handlers, retry and dead-letter policies, backlog alerts and safe release practices.
Secure and Private
Identity-based connections, private networking, edge protection and least-privilege roles.
Governed and Efficient
Policy-enforced standards, tuned scaling, cost alerts, repeatable templates and rehearsed recovery.
19Frequently Asked Questions
Short, practical answers to questions that come up again and again.
No. Each function has exactly one trigger. If two events need the same logic, create two small functions that share common code.
Most triggers guarantee at-least-once delivery. A crash or timeout before completion is recorded causes redelivery. Make your function idempotent so repeats are harmless.
A cold start occurred because no instance was ready. Use always-ready instances, Premium prewarmed workers, a smaller package and lighter startup work for latency-sensitive functions.
Microsoft recommends Flex Consumption for new serverless apps. Choose Premium for steady traffic with always-warm capacity and richer options, Dedicated to reuse existing App Service capacity, and Container Apps if you standardize on containers.
Plans allow long execution timeouts, but an HTTP response must still be sent within about 230 seconds because of the load balancer idle limit. Use an asynchronous pattern for longer work.
Automatic scale-out raised concurrency beyond what the database accepts. Cap maximum instances, tune per-instance concurrency, pool connections and use a queue to smooth bursts.
No. Slots exist on Premium and Dedicated plans and in limited numbers on legacy Consumption, but not on Flex Consumption. Use a second app or rolling updates instead, and keep staging triggers away from production events.
No. Keys identify an application caller and can leak. Use token-based authentication for users and keep keys for server-to-server calls behind additional controls.
The most common cause is that the host cannot reach its storage account through the private network, often because private DNS for blob, queue or table is missing. Verify storage access and DNS first.
When a process has several steps, waits, parallel branches, retries with state or human approvals. For a single short task, a plain function is simpler.
Running orchestrations replay against recorded history. A code change that alters the sequence of steps breaks that match. Plan versioning, or let old instances finish before deploying breaking changes.
A serverless plan with identity-based connections, a maximum instance setting, Application Insights with sampling, dead-letter alerts and an execution-count alert. Add always-ready capacity or private networking only where the workload demands it.
20Summary and Key Takeaways
Azure Functions gives you on-demand workers that appear when events arrive and vanish when work ends. Behind the simplicity sit a host, a scale controller, a storage account and a set of rules that every serious builder should know. Delivery is at least once, so idempotency is not optional. Scaling is automatic, so you must set boundaries that protect your dependencies and your budget. Hosting plans differ sharply in cold starts, networking and billing, and Flex Consumption is now the recommended serverless choice while older Consumption plans are being retired. Durable Functions turns fragile chains of queues into readable stateful workflows. Identity beats passwords, private networking needs private DNS, and observability must watch backlog, not just exceptions.
Key Takeaways
- One trigger, many bindings — each function has exactly one trigger, and bindings remove plumbing for inputs and outputs.
- Design for repeats — at-least-once delivery means every function must be idempotent, with clear retry, poison and dead-letter handling.
- Pick the plan on purpose — compare Flex Consumption, Premium, Dedicated and Container Apps by cold start, networking, limits and billing, and plan migration off legacy Consumption.
- Shape the scaling — set maximum instances and concurrency to protect databases and budgets, and plan partitions for stream triggers.
- Use durable workflows for stateful work — keep orchestrators deterministic, put real work in activities and avoid long synchronous chains.
- Secure with identity and layers — managed identities, Key Vault references, token-based user authentication, private endpoints and validated inputs together protect production.
- Release and observe with care — keep staging triggers away from live events, version messages, watch backlog age and dead-letter counts, and alert on execution spikes.