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
Enter fullscreen mode Exit fullscreen mode
2. Measuring API Latency
Stopwatch — quick and surgical:
var stopwatch = Stopwatch.StartNew();
await ProcessRequestAsync();
stopwatch.Stop();
Console.WriteLine($"Elapsed: {stopwatch.ElapsedMilliseconds} ms");
Enter fullscreen mode Exit fullscreen mode
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");
}
}
Enter fullscreen mode Exit fullscreen mode
Register in Program.cs:
app.UseMiddleware<LatencyMiddleware>();
Enter fullscreen mode Exit fullscreen mode
Application Insights — production-grade:
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry(
Configuration["ApplicationInsights:InstrumentationKey"]);
}
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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);
}
Enter fullscreen mode Exit fullscreen mode
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);
}
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode
Response Compression
public void Configure(IApplicationBuilder app)
{
app.UseResponseCompression();
}
Enter fullscreen mode Exit fullscreen mode
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");
}
Enter fullscreen mode Exit fullscreen mode
5. Advanced Techniques
Message Queues — offload non-urgent work
public async Task<IActionResult> ProcessOrderAsync(Order order)
{
await _messageQueue.SendAsync(order);
return Accepted(); // responds immediately
}
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode
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.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.