Back to blog
May 17, 2025
4 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.

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.