Overview
API performance is about latency (how long one request takes, especially at P95 and P99) and throughput (how many requests per second the system sustains). Most slow APIs are slow for a few recurring reasons: too many database queries, missing indexes, large payloads, sequential calls to other services, and no caching.
The process is always measure first: trace a slow request, find where time is spent, then fix the biggest contributor. Typical fixes are caching, batching, parallelizing independent calls, pagination, compression, connection pooling, and moving slow work to background jobs.
Orders are slow not because the chef is slow at everything but because one station is overloaded, ingredients are fetched one at a time, and dishes that could cook in parallel are cooked in sequence. Fixing the bottleneck speeds up every order.
When to use it
- Endpoints miss latency targets (SLOs).
- Traffic growth is increasing costs or errors.
- Mobile clients suffer from payload size or round trips.
- Interview deep dives: 'this endpoint is slow, what do you do?'.
Where it shows up in interviews
Recognize it when: an endpoint's P99 is too high.
- Speed up a product page API
- Optimize a feed endpoint
Recognize it when: handle 10x more requests per second.
- Prepare an API for a flash sale
- Scale a read-heavy API
Where it is used in real software
OpenTelemetry, Jaeger, and Datadog APM show each span of a request (DB queries, cache calls, downstream services) to locate the slow part.
Amazon famously reported that every 100 ms of added latency cost measurable sales, which drove its focus on tail latency.
gzip or Brotli typically shrinks JSON responses by 70-90%.
Key terms
- P50 / P95 / P99
- Latency percentiles; tail latency affects many users on pages with many calls.
- N+1 queries
- One query per item in a list instead of a single batched query.
- Fan-out
- One request calls several services; latency is set by the slowest.
- Payload size
- Bytes transferred; affects network time, especially on mobile.
A method for fixing a slow endpoint
- 1Measure
Collect P50/P95/P99 and trace slow requests end to end.
- 2Find the dominant cost
Database, downstream calls, serialization, or network transfer.
- 3Fix data access
Add indexes, remove N+1 queries, select only needed columns.
- 4Cache and batch
Cache hot reads, batch lookups, and parallelize independent calls.
- 5Shrink responses
Paginate, trim fields, compress.
- 6Move work off the request path
Send emails, generate reports, and update analytics asynchronously.
Optimizing a product page API
Initial P95 = 1,200 ms
| Change | Why it helped | P95 after |
|---|---|---|
| Start | - | 1,200 ms |
| Fix N+1 for reviews (1 + 50 queries to 2) | Fewer DB round trips | 650 ms |
| Add index on reviews(product_id, created_at) | Seek instead of scan | 420 ms |
| Call pricing and inventory in parallel | Latency = max, not sum | 260 ms |
| Cache product details in Redis (60 s) | Skip DB for hot products | 120 ms |
| Brotli compression + pagination | Smaller payload | 90 ms |
NOWChange: Start | Why it helped: - | P95 after: 1,200 ms
Each change targeted the largest remaining cost. Guessing (for example, upgrading servers first) would have cost money without fixing the queries.
Implementation
// Before: sequential calls, latency = sum of all threeasync function productPageSlow(id: string) { const product = await catalog.get(id); const price = await pricing.get(id); const stock = await inventory.get(id); return { product, price, stock };} // After: cache + parallel independent calls with a timeout budgetasync function productPage(id: string) { const cached = await redis.get(`product:${id}`); const product = cached ? JSON.parse(cached) : await catalog.get(id); if (!cached) await redis.set(`product:${id}`, JSON.stringify(product), { EX: 60 }); const [price, stock] = await Promise.all([ withTimeout(pricing.get(id), 150), withTimeout(inventory.get(id), 150).catch(() => ({ status: "unknown" })), // degrade gracefully ]); return { product, price, stock };} function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> { return Promise.race([ promise, new Promise<T>((_, reject) => setTimeout(() => reject(new Error("timeout")), ms)), ]);}Complexity and performance
a + b + c.
max(a, b, c).
Costs a little CPU.
Trade-offs
Caching gives the biggest wins but introduces staleness; choose TTLs per data type.
Parallelism cuts latency but increases concurrent load on downstream services.
Variants and related techniques
GET /products?ids=1,2,3 replaces many single calls.
Return 202 and process heavy work in queues.
Cache public responses at the CDN.
Common mistakes
- Optimizing without measuring.
Fix: Profile and trace first; fix the largest contributor.
- Looking only at averages.
Fix: Track P95/P99; tail latency is what users notice.
- Unbounded list endpoints.
Fix: Always paginate and cap limits.
Interview questions
An API endpoint is slow. How do you investigate?
Check percentiles and when it started, then trace slow requests to see time per span. Look for slow or repeated queries, missing indexes, sequential downstream calls, large payloads, and resource saturation, then fix the dominant cost and re-measure.
Why does P99 matter more than the average?
Averages hide outliers. A page that makes 20 API calls has a high chance that at least one hits the P99, so tail latency drives user-perceived performance.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Remove an N+1 query from an ORM endpoint | Easy | Batching. |
| Cut a fan-out endpoint's P99 in half | Medium | Parallelism and timeouts. |
| Prepare an API for 20x traffic during a sale | Hard | Caching, queues, capacity. |