Back to blog
May 17, 2025
1 min read

Optimising ASP.NET Core APIs for Speed and Efficiency. Practical techniques to reduce latency, CPU usage, and memory allocations

Building a working API is one thing β€” building a *fast and efficient* API is another. As your application scales or handles more users, performance becomes a critical concern.
On this page

Optimising


Building a working API is one thing β€” building a fast and efficient API is another. As your application scales or handles more users, performance becomes a critical concern.

In this article, we’ll walk through practical techniques to optimise ASP.NET Core APIs for speed and efficiency, covering compression, caching, efficient serialisation, memory pressure, streaming, and monitoring.


πŸš€ 1. Enable Response Compression

ASP.NET Core does not enable compression by default. Enabling it can drastically reduce payload sizes β€” especially for JSON-heavy responses.

πŸ“‰ Example: a 45KB JSON payload can be reduced to 8KB with Gzip compression.

builder.Services.AddResponseCompression(options =>
{
    options.EnableForHttps = true;
    options.Providers.Add<GzipCompressionProvider>();
    options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
        new[] { "application/json" });
});

builder.Services.Configure<GzipCompressionProviderOptions>(options =>
{
    options.Level = CompressionLevel.Fastest;
});

πŸ“– Response compression in ASP.NET Core


🧠 2. Use Caching Strategically

Caching reduces redundant work and improves throughput dramatically.

πŸ”Έ a) Response Caching

Response caching stores entire responses and serves them directly, saving CPU cycles and database hits.

builder.Services.AddResponseCaching();
app.UseResponseCaching();

[HttpGet]
[ResponseCache(Duration = 60)]
public IActionResult GetCachedData()
{
    return Ok(new { message = "Cached response", time = DateTime.UtcNow });
}

πŸ”Έ b) In-Memory or Distributed Caching

Use IMemoryCache or IDistributedCache to cache database or computation-heavy results.

public class MyService
{
    private readonly IMemoryCache _cache;

    public MyService(IMemoryCache cache) => _cache = cache;

    public async Task<string> GetDataAsync()
    {
        return await _cache.GetOrCreateAsync("data-key", entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
            return FetchFromDatabaseAsync();
        });
    }
}

πŸ“– Caching in ASP.NET Core


🧩 3. Use Efficient JSON Serialisation

ASP.NET Core uses System.Text.Json by default β€” it’s fast, but you can still tweak it for better results.

builder.Services.Configure<JsonOptions>(options =>
{
    options.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
    options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});

⚑ Advanced: Source Generators

Improves serialisation speed and reduces allocations.

[JsonSerializable(typeof(MyResponseModel))]
internal partial class MyJsonContext : JsonSerializerContext { }
var json = JsonSerializer.Serialize(myData, MyJsonContext.Default.MyResponseModel);

πŸ“– System.Text.Json overview


♻️ 4. Reduce Allocations and GC Pressure

Excessive allocations β†’ frequent garbage collections β†’ latency spikes. Use low-allocation patterns whenever possible.

βœ… Tips:

  • Reuse buffers with ArrayPool<T>:

    var buffer = ArrayPool<byte>.Shared.Rent(1024);
    // use buffer...
    ArrayPool<byte>.Shared.Return(buffer);
  • Prefer readonly struct when the object is immutable and passed by reference.

  • Avoid unnecessary .ToList(), .Select() and LINQ operations in hot paths.

  • Don’t return IEnumerable<T> if the data is already materialised.

πŸ“– Performance best practices for .NET


🌊 5. Stream Data When Possible

When returning large datasets, avoid loading the full result into memory.

βœ… Use IAsyncEnumerable<T>:

[HttpGet]
public async IAsyncEnumerable<MyItem> StreamItems()
{
    await foreach (var item in _repository.GetItemsAsync())
    {
        yield return item;
    }
}

This supports efficient streaming over HTTP/2, reducing memory pressure and improving time-to-first-byte.


βš™οΈ 6. Use Dependency Injection Efficiently

Registering your services properly avoids unnecessary instantiations and memory waste.

βœ… Guidelines:

  • Singleton: for stateless and thread-safe services.
  • Scoped: for per-request lifetime.
  • Avoid injecting large services if only used conditionally.
  • Consider lazy loading via Lazy<T> or factory methods.
services.AddSingleton<HttpClient>();
services.AddScoped<IMyService, MyService>();

πŸ“– Dependency injection in ASP.NET Core


πŸ“ˆ 7. Measure and Monitor

Don’t optimise blindly. Always profile first.

βœ… Tools:


βœ… Performance Checklist

  • Is response compression enabled?
  • Are you using System.Text.Json with proper settings?
  • Are you caching expensive operations or results?
  • Have you measured and reduced memory allocations?
  • Are you streaming large data instead of loading all at once?
  • Is your dependency injection properly scoped?
  • Are you monitoring real performance metrics in production?

πŸ”š Conclusion

Efficient APIs aren’t just about fast code β€” they’re about thoughtful design, strategic trade-offs, and the right tools. These techniques help reduce latency, CPU load, and memory usage, making your APIs ready for real-world scale.

Optimise smartly. Profile often. And ship fast.