You’ve written the code, tested it locally, and everything works. Then in production, your API call fails with a timeout error. No response, no data, no idea what happened on the other end.
API timeouts rank among the most frustrating failures in software development — and the most frequent.
What Is an API Timeout?
A timeout occurs when a client gives up waiting for a server to respond. The server might still be processing the request, but the client has already closed the connection and moved on.
There are two types:
- Connection timeout: The client couldn’t establish a Transmission Control Protocol (TCP) connection at all
- Read timeout: The connection was established, but the server didn’t send a response in time
Most timeout errors in production are read timeouts — the server is doing work but taking too long.
Common Causes of API Timeouts
1. Slow database queries
The most frequent cause. A query that runs fine with 1,000 rows becomes unusable with 10 million. Missing indexes, full table scans, and lock contention all play a role.
-- This might take 30 seconds on a large table without an index
SELECT * FROM orders WHERE customer_email = '[email protected]'
ORDER BY created_at DESC;
2. Downstream service failures
Your API calls another API, which calls yet another. If any service in the chain is slow or down, the entire request stalls.
Your App → Payment API → Bank API → Fraud Detection API
↑ This is slow today
The deeper the problem sits in the chain, the harder it is to diagnose.
3. Resource exhaustion
When a server runs out of CPU cycles, memory, or available connections, new requests pile up and eventually timeout:
- Connection pool exhaustion: All database connections are in use
- Thread/worker saturation: All request handlers are busy
- Memory pressure: The operating system starts swapping to disk
4. Network issues
Packet loss, Domain Name System (DNS) failures, and routing problems between data centers cause intermittent timeouts that are notoriously hard to reproduce.
5. Large payloads
Transferring megabytes of data over a slow connection can exceed timeout limits, even if the server finished processing in milliseconds.
6. Cold starts
Serverless functions that haven’t run recently must initialize their runtime, load dependencies, and establish connections before handling requests. This “cold start” can add 1–10 seconds to the first invocation.
Platform Timeout Limits
Every platform enforces timeout limits, and the differences are dramatic:
| Platform | Default Timeout | Max Timeout |
|---|---|---|
| Vercel (Hobby) | 10s | 10s |
| Vercel (Pro) | 60s | 300s |
| Netlify Functions | 10s | 26s |
| AWS Lambda | 3s | 900s (15 min) |
| AWS API Gateway | 30s | 30s |
| Cloudflare Workers | — | 30s (CPU time) |
| Google Cloud Functions | 60s | 540s (9 min) |
| Heroku | 30s | 30s |
Notice that even AWS Lambda’s generous 15-minute limit is paired with API Gateway’s hard 30-second cap. Your Lambda might support long tasks, but behind API Gateway, the client still hits a 30-second wall.
What Happens When a Timeout Occurs?
This is the dangerous part. When your client times out:
- The client gets an error — but the server might still be processing
- Work may be duplicated — if the client retries, the server processes the same request twice
- Data can become inconsistent — the first request might complete after the retry, creating duplicates
- Resources are wasted — the server finishes work that nobody will ever read
Timeouts aren’t an error handling problem alone — they threaten data consistency.
How to Handle API Timeouts
Set explicit timeouts
Never rely on defaults. Set timeouts appropriate for each outbound call:
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch('https://api.example.com/data', {
signal: controller.signal,
});
clearTimeout(timeout);
return response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request timed out after 5 seconds');
}
throw error;
}
Implement retries with backoff
Transient timeouts often resolve on retry. Use exponential backoff to avoid overwhelming the server:
async function fetchWithRetry(url, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fetch(url);
} catch (error) {
if (attempt === maxRetries - 1) throw error;
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
}
}
}
Use circuit breakers
After repeated failures, stop calling the failing service for a cooldown period. This prevents cascading failures across your system.
Offload to a task queue
For API calls that routinely exceed your timeout window, stop forcing them to be synchronous. Offload them to a task queue:
// Your endpoint responds in ~50ms
app.post('/process', async (req, res) => {
const task = await asyncqueue.tasks.create({
callbackUrl: 'https://slow-service.example.com/process',
payload: req.body,
webhookUrl: 'https://your-app.com/api/on-complete',
retries: 5,
backoff: 'exponential',
});
res.json({ taskId: task.id, status: 'queued' });
});
AsyncQueue imposes no timeout limit on task execution. Whether the API takes 30 seconds or 30 minutes, the task completes and stores the result.
Prevention Checklist
- Set explicit timeouts on every outbound HTTP call
- Add database query timeouts and index critical columns
- Monitor p95 and p99 response times, not just averages
- Implement circuit breakers for critical downstream services
- Use connection pooling with appropriate limits
- Offload long-running operations to a task queue
- Make all API handlers idempotent to handle safe retries
- Set up alerts for timeout rate spikes
Conclusion
API timeouts aren’t bugs — they’re inevitable. Networks fail, services slow down, and databases choke under load. The difference between a fragile system and a resilient one lies in how you handle these failures.
For short operations, retries and circuit breakers work well. For anything that might exceed your platform’s timeout limit, a task queue like AsyncQueue ensures the work completes — regardless of duration.