Azure & Cloud Architecture
Azure App Service vs Azure Functions: Which Should You Use?
Choose App Service or Functions from the workload’s execution and ownership model-not from a simplistic “web app versus serverless” label. Both can host .NET code; their scaling and failure boundaries differ.

The trigger is not the architecture
An HTTP trigger does not make a workload a good Function, and a background task does not require serverless hosting. Decide who owns the process lifetime, how work is accepted, how concurrency is bounded, what startup latency is tolerable and whether several operations should scale together.
App Service gives an application-shaped host with predictable always-on instances, conventional ASP.NET Core middleware and one deployment unit. Functions gives trigger-shaped execution and event-driven scaling, with hosting-plan behaviour that materially affects networking, cost, cold start and deployment.
Decision matrix
| Pressure | App Service | Azure Functions |
|---|---|---|
| Long-lived web API | Natural fit | Possible, but trigger host adds little |
| Bursty event processing | Needs worker scaling design | Strong fit |
| Predictable steady traffic | Simple capacity/cost | Premium or dedicated may converge on similar economics |
| Scale to zero | No | Flex Consumption supports it |
| Low-latency first request | Always-running instances | Use always-ready/Premium or accept startup risk |
| Several tightly coupled endpoints | One coherent app | A function app can become a fragmented web app |

Functions plans change the answer
“Functions has cold starts” is incomplete. Flex Consumption is the current recommended serverless plan and supports event-driven scaling, virtual-network integration and optional always-ready instances. Premium keeps prewarmed capacity and supports demanding continuous workloads. Dedicated hosting runs on App Service capacity. The plan is an architectural choice, not a billing footnote.
Always-ready instances reduce cold start by reintroducing baseline cost. At sustained traffic, execution pricing may exceed a right-sized dedicated host. Model real executions, memory, concurrency, idle periods and downstream cost; request count alone is misleading.
Scaling moves bottlenecks downstream
Functions can add consumers faster than a database, SaaS API or storage account can tolerate. Set per-instance concurrency, connection limits and queue batch/prefetch deliberately. App Service scale-out has the same downstream problem, but its instance count is often easier to reason about.
Autoscale reacts after signals change. Keep enough warm capacity for latency objectives and use queues to absorb bursts where delayed completion is acceptable. Scaling HTTP workers cannot rescue a synchronous dependency already saturated.
Durability is not supplied by the host
A Function trigger can redeliver after failure. An App Service worker can restart during work. Durable acceptance, idempotent effects, poison handling and reconciliation are application responsibilities in both models. Durable Functions can coordinate long-running workflows, but its replay model and storage become part of the design.
Do not return success from either host before important background work is durably recorded. Do not assume a platform retry means an operation is safe to repeat.
Deployment boundaries should match ownership
A function app is a scale and deployment boundary. Packing unrelated high-volume and latency-sensitive triggers together can couple their resources and release cadence. Splitting every function into its own app creates operational sprawl. Group by scaling profile, shared configuration and team ownership.
App Service slots support warm-up and swap for many plans. Functions deployment options vary by hosting plan; Flex Consumption uses package-based deployment and current site-update strategies. Database and message contracts still need old/new version overlap whichever platform moves code.
Observability differs at the edges
App Service telemetry naturally follows HTTP requests and hosted workers. Functions requires trigger-aware metrics: invocation delay, retries, queue age, lease/lock loss and poison outcomes. In both, trace business operations across HTTP, messages and dependencies rather than counting host invocations as completed work.
Cost telemetry needs the same context. A cheap invocation that triggers expensive AI, database or egress work is not a cheap feature.
A hybrid is normal when boundaries are real
A common design hosts the user-facing API in App Service and event-driven workers in Functions. Share contracts and application libraries cautiously, not deployment assumptions or scoped state. Communicate through durable queues when asynchronous completion is acceptable.
Do not create a Function merely to call code that could run as an in-process worker with identical scale and ownership. Each host adds configuration, identity, telemetry and deployment surface.
Recommendation
- Choose App Service for cohesive, continuously available web applications and APIs with predictable runtime needs.
- Choose Functions for independently scalable event-driven work, especially bursty triggers and managed integrations.
- Choose the Functions hosting plan before estimating latency or cost.
- Use both only where workload boundaries justify separate deployment and scaling.
- Load-test downstream limits and failure recovery, not only host throughput.
App Service is usually the clearer home for a coherent web application
When HTTP requests, authentication, routing, administration and background coordination belong to one product, App Service provides a straightforward deployment and scaling boundary. ASP.NET Core runs with familiar middleware, health checks and process semantics.
Functions can expose HTTP triggers, but decomposing every controller action into a separate function rarely creates useful independence. Shared domain code, data and release cadence remain coupled while local development and observability become more distributed.
Choose Functions for trigger-oriented units with genuinely independent scaling or lifecycle, not because the word “serverless” implies less architecture.
Hosting plan determines cold start, networking and cost
“Azure Functions” is not one runtime profile. Consumption-oriented, Flex Consumption, Premium and dedicated hosting differ in scaling, pre-warmed capacity, network capability, limits and charging. App Service plans likewise vary by tier and instance commitment.
Model actual traffic: steady baseline, burst size, execution duration, memory, concurrency and idle periods. Per-execution pricing may favour sporadic work; permanently warm or long-running workloads can favour reserved capacity. Include storage, telemetry and downstream cost.
Cold start is a journey constraint, not a benchmark curiosity. If the first request after idle sits inside an interactive deadline, use an appropriate plan or host rather than hiding it behind retries.
Scale-out does not make dependencies elastic
Functions can add consumers quickly enough to overwhelm SQL connection pools, third-party APIs or a storage partition. App Service autoscale can do the same. Set concurrency and backpressure from downstream capacity rather than maximising host throughput.
Queue-triggered workloads need to monitor age and completion, not only invocation count. A host that repeatedly retries poison work is busy but not productive. Apply idempotency because messages can be delivered more than once.
For HTTP workloads, global scale can amplify cache misses and connection establishment. Warm connections, rate limits and per-tenant fairness remain application responsibilities.
Execution timeout does not define business durability
A Function completing successfully only proves one invocation returned. Long workflows need durable state, replay-safe steps and compensation. Durable Functions can provide orchestration semantics, but activities must remain deterministic or idempotent according to their role.
An App Service background worker also needs persistent intent, queue semantics and recovery. Hosting a loop continuously does not guarantee a job survives deployment or duplicate acquisition.
Use queues and workflow state when work outlives an HTTP request or process. Pick the host after defining the delivery and consistency contract.
Deployment unit should follow ownership and blast radius
Putting unrelated triggers in one Function App couples configuration, identity, deployment and scaling. Splitting every function creates a fleet with repeated infrastructure and harder coordination. Group functions that share ownership, security boundary and release cadence.
App Service deployment slots support validation and traffic swap, but stateful dependencies still require compatible changes. Function deployments also overlap running executions and queued contracts; neither platform removes release engineering.
Separate a workload when independent scaling or failure isolation provides measurable value. Repository layout alone is not an architecture boundary.
Identity and networking can dominate the decision
Both services support managed identity, but plan and network requirements affect private endpoints, outbound routing and scaling. Verify current capabilities for the chosen tier rather than assuming a feature listed for the product applies to every plan.
Grant each deployment unit only the data and messaging permissions it needs. A Function App containing many unrelated triggers often accumulates the union of their privileges, increasing blast radius.
Stable outbound addresses, private DNS and dependency firewalls need end-to-end testing. A function that scales but cannot reach a private dependency is not elastic.
Operational experience differs more than C# code
App Service aligns telemetry around long-lived applications and HTTP journeys. Functions add trigger, binding and invocation semantics; correlation across queues and orchestrations must be preserved deliberately. Short-lived executions need enough time to flush telemetry.
Monitor cold starts, invocation duration, retries and trigger backlog for Functions. For App Service, watch instance health, request tails, worker saturation and background queues. In both, add business outcome metrics.
Local emulation does not reproduce cloud identity, scale or network behaviour. Maintain deployed integration tests and rehearse dependency outage and platform scaling.
A practical recommendation
Use App Service by default for a cohesive ASP.NET Core web application, especially with sustained HTTP traffic, shared middleware and predictable always-on operation. Use Functions for asynchronous, scheduled or event-triggered units whose independent scaling and billing model are genuine advantages.
A hybrid is often correct: App Service owns interactive journeys and persists intent; Functions process isolated queue or timer workloads. Keep contracts explicit and resist splitting work that still shares one transaction or release.
The choice is not permanent, but moving stateful workflows is expensive. Prototype the uncertain plan constraints-cold start, private networking, burst concurrency and unit cost-with realistic traffic before committing.
Related C3 Software guides
Frequently asked questions
Are Azure Functions cheaper than App Service?
Only for particular traffic and plan profiles. Model execution, memory, always-ready capacity and downstream cost.
Do Functions always have cold starts?
Scale-to-zero plans can. Flex Consumption offers improved startup and optional always-ready instances; Premium keeps prewarmed capacity.
Can App Service run background workers?
Yes, but important work still needs durable acceptance and shutdown-aware execution.
Should every event handler be a separate Function app?
No. Group handlers with compatible scaling, configuration and ownership while avoiding unrelated workload coupling.
Choose Azure hosting from workload evidence
C3 Software Limited has built and supported business software in the UK since 2001. If hosting choices are being driven by labels rather than production constraints, talk to C3 Software about an Azure architecture review.