Why HttpClient lifetime matters
HttpClient is a high-level client, but each request ultimately depends on connections managed by SocketsHttpHandler. Creating and disposing a client for every request can churn connection pools and contribute to socket exhaustion.
Use a long-lived client or obtain clients from IHttpClientFactory.
A typed client
public sealed class CatalogClient(HttpClient httpClient)
{
public async Task<Product?> GetAsync(
int id,
CancellationToken cancellationToken)
{
using var response = await httpClient.GetAsync(
$"/products/{id}",
HttpCompletionOption.ResponseHeadersRead,
cancellationToken);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<Product>(
cancellationToken: cancellationToken);
}
}
Registration:
services.AddHttpClient<CatalogClient>(client =>
{
client.BaseAddress = new Uri("https://catalog.example");
client.Timeout = TimeSpan.FromSeconds(10);
});
Nested generic syntax check
Real applications often keep retry or status information in nested generic structures:
var attemptsByEndpoint = new Dictionary<string, List<int>>();
The type above should remain visible exactly as written.
Timeout and cancellation
- A timeout protects the caller from waiting forever.
- A
CancellationTokenlets upstream work cancel downstream I/O. - Treat cancellation differently from a timeout or a network failure in logs.
- Prefer explicit budgets: the total operation timeout must include every retry.
Retry rules
Retry only transient failures and only when the operation is safe to repeat. Add jitter to avoid synchronized retry storms. Do not retry validation errors, authentication failures, or an unsafe write unless idempotency is guaranteed.
Interview checklist
- Explain why per-request disposal can be harmful.
- Describe what
IHttpClientFactorymanages. - Distinguish request timeout, cancellation, and connection timeout.
- Explain retry amplification.
- Mention observability: latency, status, retry count, and correlation ID.