You've created an API, it works, but users complain about slow responses. You look at the code — everything seems to be fine. The database works, the queries are simple, and the response time is still in seconds, not milliseconds. Is this a familiar situation?
The problem is that most developers check the obvious things: indexes in the database, the complexity of algorithms, and the size of the data. But the real brakes often hide in places where few people look. Let's take a look at 12 of these non-obvious reasons for slow API performance.

1. DNS requests for each external call
Your API accesses an external service — a payment system, analytics service, or other microservice. And every time it makes a DNS request to find out the IP address. This can add 20-200 milliseconds to each request.
Many HTTP clients do not cache DNS by default. If your API makes 10 external requests per user request, you lose up to 2 seconds just on DNS.
Solution: configure DNS caching in the HTTP client or use connection pooling with connection reuse.
2. Serialization of data in an inefficient format
JSON is convenient, but slow. If your API transfers large amounts of data between services, JSON serialization and deserialization can take up a significant portion of the request processing time.
For example, serializing an array of 10,000 objects in JSON can take 50-100 milliseconds in Python. If this happens several times per request, the time accumulates.
Alternatives: for internal communications between services, try MessagePack, Protocol Buffers, or even a simple binary format. They work much faster.
3. Synchronous and redundant logging
You log every request, every response, every action. This is correct for debugging, but if logs are written synchronously to a file or database, each write operation blocks the execution of the code.
Writing to a file can take 5-20 milliseconds. If you write 10 logs per request, it's already 50-200 milliseconds of pure latency.
Solution: use asynchronous logging with a buffer, send logs to a separate process or service, reduce the level of detail in production.
4. Lack of prepared queries to the database
Even with indexes, the database can work slowly if you send a new SQL query each time instead of using prepared statements. The database is forced to parse the query each time, build an execution plan, and only then execute it.
Prepared queries are cached by the database, and re-execution occurs almost instantly. Savings — up to 30-40% of the time for simple queries.
5. Cold starts in cloud functions
If you use serverless architecture (AWS Lambda, Google Cloud Functions), a cold start can add from 500 milliseconds to several seconds to the first request after a period of inactivity.
The problem is exacerbated if your function is heavy: many dependencies, large libraries, long initialization. The user sees the brakes, although the code itself works quickly.
Solution: use provisioned concurrency for critical functions, optimize the image size, and take initialization outside the handler function.
6. Lack of a pool of connections to the database
Each new connection to the database takes several tens of milliseconds to establish a TCP connection, authentication, and initialization. If your API creates a new connection for each request, you're wasting time.
Connection pooling reuses existing connections. This is a basic optimization, but many novice developers forget about it or configure it incorrectly.
Check the settings: are there enough connections in the pool? Are they closing too fast? Is the timeout configured correctly?

7. Middleware is executed for all requests
You have middleware for authentication, logging, CORS processing, and validation. All this is done for each request, even for static files or a health-check endpoint.
If middleware makes a request to a database or external service to validate a token, this adds a delay to all requests without exception.
Solution: optimize the order of middleware, move easy checks forward, exclude unnecessary paths, and cache token validation results.
8. Garbage Collection at a critical moment
Languages with automatic memory management (Python, Java, Go) periodically run a garbage collector. In most cases, this is invisible, but if your API accumulates a lot of objects in memory, GC can work right during the processing of the request and freeze the execution for tens or hundreds of milliseconds.
This is especially noticeable in Python with its GIL and in Java with incorrectly configured GC parameters.
Monitor GC pauses, configure garbage collector settings, and avoid creating unnecessary objects in hot code areas.
9. Blocking operations in asynchronous code
You use async/await, FastAPI or Node.js, but somewhere in the code you make a synchronous call: reading a file, querying a database without an asynchronous driver, calling an API through regular requests.
This blocks the event loop, and all other requests are queued. One slow query slows down the entire server.
Solution: use only asynchronous libraries, move blocking operations to the thread pool or separate processes, check all code for synchronous calls.
10. No timeout on external requests
Your API accesses an external service, but does not set a timeout. If the external service slows down or stops responding, your request hangs for minutes until the default timeout of the operating system occurs.
The user sees an endless loading, and your API consumes resources waiting for a response that may never come.
Always set reasonable timeouts: 5-10 seconds for external APIs, 1-2 seconds for internal services. It is better to return an error quickly than to make the user wait.
11. Reprocessing of identical requests
The user clicked the button several times, or the frontend sends a retry on timeout. Your API receives the same requests and honestly processes each of them: it makes requests to the database, calculations, and sends emails.
This is not only slow, but can also lead to data duplication and incorrect behavior.
Solution: use idempotency keys, cache the results of recent queries, and block repeated submissions on the frontend.
12. Metrics and monitoring are a drag on their own
Ironically, performance measurement tools can themselves become a source of brakes. Collecting detailed metrics, tracing each request, sending data to monitoring systems — all this requires resources.
If you collect too many metrics or send them synchronously, it eats up CPU time and adds delays.
Be reasonable: collect only the necessary metrics, use sampling for tracing, send data asynchronously and in batches.
What to do next?
Now you know 12 non-obvious reasons why the API can slow down. The next step is a systematic check: add profiling, measure the time at each stage of request processing, and find bottlenecks.
Remember: performance is not a one-time optimization, but a continuous process. Monitor, measure, improve.
Code is not just an app, but your personal mentor in the world of programming. It explains everything in simple words, helps to consolidate knowledge in practice and gives cool achievements for success 🏅
And we also have a cool Telegram channel with a friendly community where you can ask any question and get help from experienced developers. Join us!
