Cold starts are the most-asked-about performance question for .NET on Lambda, and also the most over-corrected for. Before reaching for the heaviest fix, it's worth understanding exactly what's slow and why.

Key takeaways
  • A cold start is runtime initialization plus your static initializers — JIT compilation is usually the biggest .NET-specific cost.
  • Provisioned Concurrency and SnapStart both solve cold starts, but they're mutually exclusive for the same function version — pick one.
  • SnapStart for .NET works by snapshotting an initialized execution environment; it needs deterministic init code and has restrictions worth reading before you adopt it.
  • Native AOT compilation removes the JIT entirely and gives the fastest cold starts, at the cost of losing dynamic reflection-heavy libraries.

What's actually slow

A .NET cold start breaks down into three phases: downloading and extracting your deployment package, initializing the .NET runtime and JIT-compiling the methods on the startup path, and running your own static initializers and DI container setup. For a typical ASP.NET-style Lambda with a few dependencies wired up, the runtime-and-JIT phase usually dominates.

Option 1: Provisioned Concurrency

AWS keeps a specified number of execution environments initialized and ready, so invocations hit a warm environment instead of triggering a cold start. It's the most predictable fix, and the most expensive — you pay for the reserved capacity whether or not it's invoked.

bash
aws lambda put-provisioned-concurrency-config \
  --function-name order-processor \
  --qualifier prod \
  --provisioned-concurrent-executions 5

Option 2: SnapStart

SnapStart for .NET (available for .NET 8 and later, including .NET 10) takes a snapshot of a fully initialized execution environment — after your static constructors and DI container have already run — and restores from that snapshot on invocation instead of booting from scratch. It's billed like a normal function (no reserved-capacity cost) and can cut cold starts from seconds to well under a second for typical workloads.

It comes with real constraints: SnapStart is not compatible with Provisioned Concurrency on the same version, it doesn't support attaching an EFS filesystem or more than 512 MB of ephemeral storage, and any state captured at snapshot time (like a random seed, a cached credential, or a connection that goes stale) needs to be re-initialized using a runtime hook that fires after restore.

csharp
// Runtime hook: re-establish anything that shouldn't survive a snapshot/restore
[assembly: LambdaSnapStartInitializer]

public class Function
{
    [SnapStartRestoreHook]
    public void OnRestore()
    {
        // re-seed RNGs, refresh short-lived credentials, re-open connections
    }
}

Option 3: Native AOT

Compiling your function ahead-of-time removes JIT compilation from the cold start path entirely. It's the fastest option and works well for functions with a small, well-understood dependency graph — but it drops support for runtime reflection-heavy patterns (some ORMs, some serializers) unless they explicitly support trimming and AOT.

xml
<PropertyGroup>
  <PublishAot>true</PublishAot>
  <InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

What we actually recommend

For most business APIs on Lambda: start with SnapStart, since it requires the least code change and has no ongoing reserved-capacity cost. Move to native AOT for latency-sensitive, high-traffic functions where you control the full dependency graph. Reserve Provisioned Concurrency for the cases SnapStart can't cover — functions using EFS or ephemeral storage above 512 MB, or workloads where you need absolute latency guarantees regardless of cost.

Before you decideWhichever option you pick, measure with realistic payloads and dependencies, not a bare “Hello World” handler — a DynamoDB client, a JSON serializer, and a couple of DI registrations change the numbers meaningfully.

Need help with this in production?

astrivo helps teams design, build, and modernize systems like this. Let's talk about your context.