Azure & Serverless

Azure Functions Cold Start: Optimization Without Premium

11 min read
#azure-functions#serverless#performance#cold-start
Georg Pfeiffer
Georg Pfeiffer
Senior Solution Architect

Azure Functions are powerful, but cold starts can turn a snappy API into a frustrating experience. After optimizing Functions across several production projects, here are the strategies that actually move the needle — without reaching for the Premium plan.

Measuring Cold Start

Before optimizing, measure. Cold start time varies by runtime, dependencies, and region. Use Application Insights to track it:

kusto
requests
| where timestamp > ago(24h)
| extend coldStart = tobool(customDimensions["_MS_ProcessedByMetricExtraction"])
| summarize
    avg(duration),
    percentile(duration, 95),
    count()
    by bin(timestamp, 1h), coldStart

In my experience, .NET 8 isolated worker Functions cold-start in 2-4 seconds. With optimization, you can get that under 1.5 seconds on the Consumption plan.

Strategy 1: Trim Your Dependencies

Every NuGet package adds to startup time. Audit your dependencies ruthlessly:

xml
<!-- before: 45 packages, 8-second cold start -->
<PackageReference Include="MediatR" Version="12.0.0" />
<PackageReference Include="AutoMapper" Version="13.0.0" />
<PackageReference Include="FluentValidation" Version="11.0.0" />
<PackageReference Include="Serilog.Sinks.Everything" Version="*" />

<!-- after: 20 packages, 3-second cold start -->
<!-- removed AutoMapper: manual mapping for 4 DTOs is fine -->
<!-- removed FluentValidation: data annotations for simple validation -->
<!-- trimmed Serilog to just the sinks we use -->

The biggest wins come from removing packages that pull in large dependency trees. `AutoMapper` and `FluentValidation` are common offenders in small Functions that don't need them.

Strategy 2: Lazy Initialization

Not every HTTP trigger needs every service. Initialize expensive resources lazily:

csharp
public class OrderFunction
{
    private readonly Lazy<ServiceBusClient> _serviceBus;
    private readonly IOrderRepository _orders;

    public OrderFunction(
        IOrderRepository orders,
        IConfiguration config)
    {
        _orders = orders;
        _serviceBus = new Lazy<ServiceBusClient>(() =>
            new ServiceBusClient(
                config["ServiceBusConnection"]));
    }

    [Function("GetOrder")]
    public async Task<IActionResult> GetOrder(
        [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req,
        string id)
    {
        // ServiceBusClient not initialized for read-only operations
        return new OkObjectResult(await _orders.GetByIdAsync(id));
    }
}

Strategy 3: ReadyToRun Compilation

Publish with ReadyToRun to pre-compile assemblies to native code:

xml
<PropertyGroup>
    <PublishReadyToRun>true</PublishReadyToRun>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>

This increases the deployment package size but significantly reduces JIT compilation time during cold start. In my tests, this alone saves 300-500ms.

Strategy 4: The Timer Trigger Keep-Warm

The simplest approach: a timer trigger that runs every few minutes to keep the instance warm:

csharp
[Function("KeepWarm")]
public void KeepWarm(
    [TimerTrigger("0 */4 * * * *")] TimerInfo timer,
    FunctionContext context)
{
    context.GetLogger("KeepWarm")
        .LogInformation("Keep-warm ping at {Time}", DateTime.UtcNow);
}

This isn't free — you pay for the executions — but at ~6 cents per million executions, it's dramatically cheaper than the Premium plan. It doesn't guarantee zero cold starts (scale-out still causes them), but it eliminates the most common case.

Strategy 5: Minimal Startup Configuration

The isolated worker model's `Program.cs` runs on every cold start. Keep it lean:

csharp
var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices(services =>
    {
        // register only what's needed
        services.AddSingleton<IOrderRepository, OrderRepository>();
        services.AddHttpClient<IExternalApi, ExternalApiClient>();
    })
    .Build();

host.Run();

Avoid scanning assemblies for auto-registration, loading configuration from remote sources during startup, or initializing database connections eagerly.

When to Just Use Premium

If your P95 cold start exceeds your SLA requirements and the above strategies aren't enough, the Premium plan with pre-warmed instances is the right call. The cost difference is significant ($150+/month vs cents), but some workloads justify it — particularly user-facing APIs where latency directly impacts experience.

Key Takeaways

  • Measure cold start with Application Insights before optimizing
  • Trim dependencies — fewer packages means faster startup
  • Use lazy initialization for services not needed by every trigger
  • Enable ReadyToRun compilation for 300-500ms savings
  • A timer keep-warm is cheap insurance against the most common cold starts

Share this article

Georg Pfeiffer

About the Author

Georg is a senior solution architect specializing in .NET, Azure, and Dynamics 365. He helps organizations design and build scalable, maintainable enterprise systems. When he's not writing code, he's writing about it here.

Learn more about Georg

© 2026 georgpfeiffer.dev. All rights reserved.

Built with SvelteKit & Tailwind CSS