API latency plays a crucial role in performance and user experience. High latency frustrates users, reduces scalability, and increases infrastructure costs. This guide dives deep into causes, measurement, and optimization strategies for C# .NET APIs.
1. Understanding API Latency
API latency is the total time from a client sending a request to receiving a response — covering network transmission, server-side processing, and database interactions.
Types of latency:
- Network latency — distance, bandwidth, congestion. Fix: CDNs.
- Processing latency — inefficient code, blocking operations. Fix: async programming.
- Database latency — slow queries, missing indexes. Fix: query optimization, caching, connection pooling.
Client → (Network) → API Gateway → (Processing) → Database → (DB latency) → API Gateway → Client
2. Measuring API Latency
Stopwatch — quick and surgical:
var stopwatch = Stopwatch.StartNew();
await ProcessRequestAsync();
stopwatch.Stop();
Console.WriteLine($"Elapsed: {stopwatch.ElapsedMilliseconds} ms");
Middleware timing — covers every request:
public class LatencyMiddleware
{
private readonly RequestDelegate _next;
public LatencyMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context)
{
var sw = Stopwatch.StartNew();
await _next(context);
sw.Stop();
Console.WriteLine($"Request latency: {sw.ElapsedMilliseconds} ms");
}
}
Register in Program.cs:
app.UseMiddleware<LatencyMiddleware>();
Application Insights — production-grade:
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry(
Configuration["ApplicationInsights:InstrumentationKey"]);
}
Other tools: Postman (endpoint testing), JMeter (load testing), Grafana + Prometheus (dashboards), Jaeger / OpenTelemetry (distributed tracing).
3. Latency in C# .NET — How Requests Flow
Client → Web Server (Kestrel/IIS) → Middleware Pipeline → Controller → DB/Logic → Middleware → Response
Synchronous vs asynchronous — the single biggest lever:
// ❌ Synchronous — blocks the thread
public IActionResult GetData()
{
var data = _service.GetData();
return Ok(data);
}
// ✅ Asynchronous — frees the thread for other requests
public async Task<IActionResult> GetDataAsync()
{
var data = await _service.GetDataAsync();
return Ok(data);
}
Common bottlenecks:
| Bottleneck | Fix |
|---|---|
| Blocking I/O |
async/await throughout |
| Slow DB queries | Indexes, AsNoTracking() for reads |
| Heavy middleware | Remove unnecessary steps, async logging |
| Large serialization |
System.Text.Json, smaller payloads |
4. Optimizing API Latency — Best Practices
Async/Await
public async Task<IActionResult> GetUserDataAsync()
{
var data = await _databaseService.GetUserDataAsync();
return Ok(data);
}
In-Memory Caching
public async Task<User> GetUserByIdAsync(int userId)
{
if (!_memoryCache.TryGetValue(userId, out User user))
{
user = await _databaseService.GetUserByIdAsync(userId);
_memoryCache.Set(userId, user, TimeSpan.FromMinutes(10));
}
return user;
}
Response Compression
public void Configure(IApplicationBuilder app)
{
app.UseResponseCompression();
}
Efficient Serialization
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
[HttpGet]
public IActionResult GetUser(int userId)
{
var user = _databaseService.GetUserById(userId);
return Content(JsonSerializer.Serialize(user, _jsonOptions), "application/json");
}
5. Advanced Techniques
Message Queues — offload non-urgent work
public async Task<IActionResult> ProcessOrderAsync(Order order)
{
await _messageQueue.SendAsync(order);
return Accepted(); // responds immediately
}
Distributed Caching with Redis
public async Task<User> GetUserByIdAsync(int userId)
{
var cache = _redis.GetDatabase();
var cached = await cache.StringGetAsync(userId.ToString());
if (!cached.IsNullOrEmpty)
return JsonSerializer.Deserialize<User>(cached);
var user = await _databaseService.GetUserByIdAsync(userId);
await cache.StringSetAsync(
userId.ToString(),
JsonSerializer.Serialize(user),
TimeSpan.FromMinutes(10));
return user;
}
Database Sharding & HTTP/2
For high-volume systems, sharding distributes data across DB instances. Upgrading to HTTP/2 or HTTP/3 enables multiplexing — multiple requests over a single connection, reducing handshake overhead.
6. Case Study: E-Commerce Checkout API
Problem: Checkout averaging 2–3 seconds. Identified via Application Insights + SQL Profiler:
- Synchronous inventory service calls
- Missing indexes on the
orderstable
Fixes applied:
- Refactored inventory check to
async/await - Added indexes on
orderstable, optimized inventory queries - Redis caching for inventory data that rarely changes
Result: Average checkout response dropped from 3 seconds → under 500ms.
7. Monitoring in Production
- Azure Monitor — API performance and resource utilization
- Prometheus + Grafana — real-time metrics and dashboards
- New Relic — end-to-end latency per endpoint
- Serilog — structured async logging
Set up alerts for latency thresholds so the team can respond to regressions before users notice.
Conclusion
Reducing API latency is not a one-time task. The core levers are: async programming, database optimization, caching (in-memory and distributed), payload compression, and middleware hygiene. Pair those with ongoing monitoring and load testing, and your .NET API stays fast as traffic scales.
Originally published on Medium.
Top comments (0)