
Azure Functions vs AWS Lambda for C# in 2026: cold starts, Native AOT, pricing math and real code. See the pricing math and pick the right platform.
If you're building serverless apps in .NET, you'll hit the Azure Functions vs AWS Lambda question sooner or later. Both run C# well. Both charge per execution. Both claim to scale to zero. In 2026, though, the details that matter have changed. Azure's in-process model is being retired, the Flex Consumption plan has matured, AWS now bills the Lambda INIT phase, and Native AOT and SnapStart have made .NET cold starts much faster. This guide compares cold starts, throughput, and real pricing math, with runnable C# code for both platforms, so you can pick based on numbers.
Azure Functions vs AWS Lambda: The 2026 Landscape at a Glance
Before looking at benchmarks, here's what each platform offers C# developers today:
- Azure Functions: The isolated worker model is now the only supported way forward. Support for the in-process model ends on November 10, 2026. Hosting options are Flex Consumption (the recommended serverless plan), the classic Consumption plan, Premium (Elastic Premium), and Dedicated App Service plans. Native AOT isn't supported for the isolated worker, but ReadyToRun is.
- AWS Lambda: Managed .NET runtimes run on Amazon Linux 2023. Your options include the managed runtime, Native AOT on the
provided.al2023OS-only runtime, SnapStart for .NET, and Graviton (ARM64) processors, which cost about 20% less per GB-second.
Both platforms move quickly, and supported .NET versions (.NET 8 LTS, .NET 10 LTS) change during the year. Check each vendor's supported-runtime page before you commit to a target framework.
Writing the Same Function in C# on Both Platforms
The best way to keep this decision reversible is to put your business logic in a plain class library and keep each cloud's code as a thin adapter. Here's the shared core:
// PriceCore/PriceService.cs (plain netstandard/net8.0+ class library)
namespace PriceCore;
public record Price(string Sku, decimal Amount, string Currency);
public interface IPriceService
{
Task<Price?> GetAsync(string sku, CancellationToken ct = default);
}
public sealed class PriceService : IPriceService
{
private static readonly Dictionary<string, Price> Catalog = new(StringComparer.OrdinalIgnoreCase)
{
["SKU-100"] = new("SKU-100", 19.99m, "USD"),
["SKU-200"] = new("SKU-200", 49.00m, "USD")
};
public Task<Price?> GetAsync(string sku, CancellationToken ct = default)
=> Task.FromResult(Catalog.TryGetValue(sku, out var p) ? p : null);
}
Azure Functions C# (Isolated Worker, ASP.NET Core Integration)
The isolated worker runs your code in its own .NET process. That lets you pick your own .NET version, use normal middleware, and use standard dependency injection. Using the ASP.NET Core integration package (Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore) gives you the familiar HttpRequest and IActionResult types.
// Program.cs
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using PriceCore;
var builder = FunctionsApplication.CreateBuilder(args);
builder.ConfigureFunctionsWebApplication();
builder.Services.AddSingleton<IPriceService, PriceService>();
builder.Build().Run();
// GetPrice.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using PriceCore;
public class GetPrice(IPriceService prices)
{
[Function("GetPrice")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "price/{sku}")] HttpRequest req,
string sku)
{
var price = await prices.GetAsync(sku, req.HttpContext.RequestAborted);
return price is null ? new NotFoundResult() : new OkObjectResult(price);
}
}
AWS Lambda C# (Lambda Annotations Framework)
The Amazon.Lambda.Annotations source generator removes most of the old API Gateway plumbing. It generates the handler and updates your CloudFormation/SAM template when you build.
using Amazon.Lambda.Annotations;
using Amazon.Lambda.Annotations.APIGateway;
using Amazon.Lambda.Core;
using Microsoft.Extensions.DependencyInjection;
using PriceCore;
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
[LambdaStartup]
public class Startup
{
public void ConfigureServices(IServiceCollection services)
=> services.AddSingleton<IPriceService, PriceService>();
}
public class Functions(IPriceService prices)
{
[LambdaFunction(MemorySize = 1024, Timeout = 10)]
[HttpApi(LambdaHttpMethod.Get, "/price/{sku}")]
public async Task<IHttpResult> GetPrice(string sku, ILambdaContext context)
{
var price = await prices.GetAsync(sku);
return price is null ? HttpResults.NotFound() : HttpResults.Ok(price);
}
}
Both versions have the same shape: a DI container, a singleton service, and a thin HTTP adapter. Since your domain logic never references Microsoft.Azure.Functions.Worker or Amazon.Lambda.Core, moving between clouds later means rewriting adapters, not the application.
Performance Comparison: Cold Starts and Throughput
Why .NET Cold Starts Happen
A cold start has three parts: provisioning a sandbox or instance, starting the .NET runtime, and running your startup code, where JIT compilation, DI container setup, and SDK client creation dominate. The first part belongs to the platform. The other two are yours to shrink.
Typical Cold Start Ranges (Indicative)
Results vary a lot with package size, memory setting, region, and dependencies. These are the typical ranges teams report for a small HTTP function:
- Lambda, managed .NET runtime, JIT: roughly 600 ms–1.5 s at 1 GB memory. It gets much worse at 128–256 MB, because CPU scales with memory.
- Lambda + SnapStart: often under 400 ms, because Lambda restores a snapshot of an already-initialized environment.
- Lambda + Native AOT: commonly 200–400 ms, with no JIT at all.
- Azure Functions Flex Consumption (isolated, ReadyToRun): commonly about 1–2 s on a cold instance. With always-ready instances it's effectively zero.
- Azure Functions classic Consumption: the slowest of these, often 2–5+ s for .NET isolated apps.
Treat these as starting points, not guarantees. Benchmark your own function with production-sized dependencies.
Native AOT on AWS Lambda
Native AOT compiles your function to a self-contained native binary, so nothing gets JIT-compiled at startup. This is currently the strongest advantage Lambda has for C# latency.
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<PublishAot>true</PublishAot>
<AssemblyName>bootstrap</AssemblyName>
<InvariantGlobalization>true</InvariantGlobalization>
<StripSymbols>true</StripSymbols>
</PropertyGroup>
The catch is that AOT needs trim-safe code. Reflection-heavy libraries, some older AWS SDK patterns, and reflection-based System.Text.Json will break or emit warnings. Use a source-generated JSON context:
using System.Text.Json.Serialization;
using PriceCore;
[JsonSerializable(typeof(Price))]
[JsonSerializable(typeof(Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest))]
[JsonSerializable(typeof(Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse))]
public partial class LambdaJsonContext : JsonSerializerContext { }
Treat every AOT/trim warning as an error. If you ignore one, you'll get a runtime failure that only shows up in production.
ReadyToRun on Azure Functions
Azure's isolated worker doesn't support Native AOT, but ReadyToRun precompiles most of your IL and cuts JIT time noticeably:
<PropertyGroup>
<PublishReadyToRun>true</PublishReadyToRun>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
</PropertyGroup>
Throughput and Concurrency: The Hidden Difference
This is the most misunderstood part of the comparison. A standard Lambda execution environment handles one request at a time, so 100 concurrent requests means 100 environments, and each one bills separately. Azure Functions instances handle many executions at once. On Flex Consumption you set per-instance concurrency, for example 16 concurrent HTTP requests on a 2 GB instance. For I/O-bound C# code that spends most of its time awaiting databases or HTTP calls, that concurrency means fewer instances, fewer cold starts, and, as the next section shows, a smaller bill.
Cost Comparison: AWS Lambda Pricing vs Azure Functions Pricing
The Published Rates (Pay-as-you-go, US Regions)
- AWS Lambda (x86): $0.20 per 1M requests plus $0.0000166667 per GB-second, billed in 1 ms increments. ARM64/Graviton: $0.0000133334 per GB-second. Free tier: 1M requests and 400,000 GB-s per month.
- Azure Functions Consumption: $0.20 per 1M executions plus $0.000016 per GB-second. Free grant: 1M executions and 400,000 GB-s. Memory rounds up to the nearest 128 MB, with a 100 ms minimum per execution.
- Azure Functions Flex Consumption (on-demand): about $0.40 per 1M executions plus about $0.000026 per GB-second. Free grant: 250,000 executions and 100,000 GB-s. It bills instance memory for the time the instance is active, not per execution, so concurrent executions share the cost.
Prices change and vary by region, so check both official pricing pages before you publish a budget.
Run the Numbers Yourself: A C# Serverless Cost Calculator
This console app compares a realistic workload: 10 million requests a month, 512 MB, 200 ms average duration. Paste it into dotnet new console and run it.
const long Requests = 10_000_000;
const decimal MemoryGb = 0.5m;
const decimal AvgSeconds = 0.200m;
const decimal FlexEffectiveConcurrency = 4m; // avg executions sharing one Flex instance
decimal gbSeconds = Requests * AvgSeconds * MemoryGb;
decimal Cost(decimal gbs, decimal freeGbs, decimal gbsRate,
long reqs, long freeReqs, decimal perMillion)
{
var compute = Math.Max(0, gbs - freeGbs) * gbsRate;
var requests = Math.Max(0, reqs - freeReqs) / 1_000_000m * perMillion;
return Math.Round(compute + requests, 2);
}
var results = new (string Name, decimal Monthly)[]
{
("AWS Lambda x86", Cost(gbSeconds, 400_000, 0.0000166667m, Requests, 1_000_000, 0.20m)),
("AWS Lambda ARM64 (Graviton)", Cost(gbSeconds, 400_000, 0.0000133334m, Requests, 1_000_000, 0.20m)),
("Azure Consumption", Cost(gbSeconds, 400_000, 0.000016m, Requests, 1_000_000, 0.20m)),
("Azure Flex (no concurrency)", Cost(gbSeconds, 100_000, 0.000026m, Requests, 250_000, 0.40m)),
("Azure Flex (concurrency x4)", Cost(gbSeconds / FlexEffectiveConcurrency, 100_000, 0.000026m, Requests, 250_000, 0.40m)),
};
Console.WriteLine($"Workload: {Requests:N0} req, {MemoryGb} GB, {AvgSeconds * 1000} ms => {gbSeconds:N0} GB-s\n");
foreach (var (name, monthly) in results.OrderBy(r => r.Monthly))
Console.WriteLine($"{name,-30} ${monthly,8:N2}/month");
The approximate output:
- Azure Flex (concurrency ×4): ~$7.80
- AWS Lambda ARM64: ~$9.80
- Azure Consumption: ~$11.40
- AWS Lambda x86: ~$11.80
- Azure Flex (no concurrency): ~$27.30
This is why "which is cheaper?" has no single answer. For CPU-bound, one-request-at-a-time work, Lambda on ARM64 wins. For I/O-bound APIs where one instance can juggle many awaits, Flex Consumption's per-instance billing can beat it.
Costs People Forget
- API front door: Lambda behind API Gateway HTTP APIs adds about $1.00 per million requests (REST APIs about $3.50). At 10M requests that can cost more than the compute. Lambda Function URLs are free but have fewer features. Azure Functions includes HTTP triggers, and API Management is optional.
- Lambda INIT billing: Since August 2025, AWS bills the initialization phase for on-demand managed-runtime functions. Slow cold starts now cost money as well as latency, which is one more reason to use Native AOT or SnapStart.
- Warm capacity: Lambda Provisioned Concurrency, Flex always-ready instances, and Azure Premium all charge a baseline even when idle. Azure Premium has an always-on minimum that often runs well over $100 a month.
- Logging: CloudWatch Logs and Application Insights ingestion can cost more than the function itself. Set sampling and log levels on purpose.
- Storage and networking: Azure Functions needs a storage account. Both platforms charge for egress and for VNet/NAT gateway setups.
Best Practices for Serverless .NET on Either Cloud
- Reuse clients. Register
HttpClient(throughIHttpClientFactory),CosmosClient,DynamoDBContext, and SDK clients as singletons. Creating them per invocation wastes CPU and can exhaust sockets. - Right-size Lambda memory. CPU scales linearly with memory, and one full vCPU arrives at about 1,769 MB. A .NET function at 1,024 MB often finishes more than twice as fast as at 512 MB, so it can cost the same or less. Use AWS Lambda Power Tuning to find the sweet spot.
- Keep startup lean. Avoid scanning assemblies, loading big configuration files, or calling remote services in
Program.csor constructors. - Use ARM64 on Lambda unless a native dependency blocks it. You get about 20% lower cost, often with equal or better performance.
- Tune Flex concurrency for I/O-bound functions, and lower it for CPU-heavy ones so requests don't compete for cores.
- Make handlers idempotent. Both platforms retry queue and event triggers, so assume at-least-once delivery.
- Always pass
CancellationTokens so timeouts stop downstream work cleanly.
Common Pitfalls to Avoid
- Staying on the Azure in-process model. Support ends November 10, 2026. Migrate to the isolated worker now, because trigger bindings and DI registration differ.
- Ignoring timeouts. Lambda's maximum is 15 minutes. Azure Consumption allows up to 10 minutes. Flex allows longer runs, but HTTP requests are still capped at about 230 seconds by the front-end load balancer. For long work, use Durable Functions or Step Functions.
- Turning on Native AOT without testing. Trimmed code can fail at runtime in reflection paths that tests never touched. Run integration tests against the published AOT binary, not the Debug build.
- SnapStart uniqueness bugs. Anything generated during init, such as random seeds, GUIDs, or cached timestamps, is shared by every restored environment. Regenerate these in runtime hooks.
- Choosing classic Consumption for new Linux apps. Microsoft steers new serverless workloads to Flex Consumption, so start there.
- Comparing list prices only. Model concurrency, gateways, logging, and warm capacity, or your estimate could be off by several times.
Which Should You Choose in 2026?
Choose AWS Lambda if…
- You need the lowest possible cold start for C#. Native AOT and SnapStart are unmatched here.
- Your workloads are CPU-bound or event-driven (S3, SQS, Kinesis, EventBridge) and you're already on AWS.
- You want fine-grained memory sizing from 128 MB to 10 GB with 1 ms billing.
Choose Azure Functions if…
- Your team works in the Microsoft stack: Entra ID, SQL Server, Cosmos DB, Service Bus, Visual Studio.
- Your APIs are I/O-bound, where per-instance concurrency on Flex lowers cost and cold starts.
- You want built-in stateful orchestration with Durable Functions, written in plain C#.
- You want HTTP endpoints without paying for a separate API gateway.
Conclusion: Azure Functions vs AWS Lambda Key Takeaways
For C# developers, neither platform clearly beats the other in the Azure Functions vs AWS Lambda choice for 2026. It depends on your workload. Lambda leads on raw cold-start speed thanks to Native AOT and SnapStart, and on price for CPU-bound work on Graviton. Azure Functions, especially Flex Consumption, often wins for concurrent I/O-bound APIs and for teams already on Azure.
- Performance: Lambda + Native AOT is the fastest .NET cold start. On Azure, use ReadyToRun plus always-ready instances.
- Cost: Lambda bills per request-environment. Flex bills per instance, and concurrency changes the math.
- Hidden costs: API Gateway, INIT billing, logging, and warm capacity often matter more than GB-second rates.
- Deadline: Move Azure in-process functions to the isolated worker before November 10, 2026.
- Portability: Keep business logic in a cloud-agnostic class library so switching clouds is cheap.
Run the cost calculator above with your own traffic numbers, deploy the same function to both clouds, and measure. An afternoon of benchmarking will tell you more than any vendor pricing page.
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.
Comments
Post a Comment