Sitemap
iyzico.engineering

The engineering behind iyzico, the sweet home for iyzicoders…

API Optimization is Not a Dark Art: Demystifying Performance

11 min readOct 2, 2025

--

Press enter or click to view image in full size

In today’s fast-paced, ever-changing world of technology, one of the most critical factors for companies to achieve their goals, increase efficiency, and hit financial targets is a strong and efficient technological infrastructure. One of the most important components of this infrastructure is Application Programming Interfaces (APIs), which allow us to expose our applications to the outside world or allow end-users to interact with front-end applications.

To provide uninterrupted service to our users, whose numbers are increasing day by day, a well-designed API that is efficient, fast, reliable, and scalable is of vital importance for the success of our company. The performance of APIs not only affects customer satisfaction, but also directly affects factors such as the efficiency of business operations and the budget allocated to technology investments.

What is API Optimization ?

In a very general sense, API optimization involves various steps aimed at increasing the performance and efficiency of APIs. Examples of these steps include reducing service response times, minimizing latency between requests, and optimizing database queries.

In this article, I’ll cover this practical techniques and reliable methods to optimize your API for better performance and user experience.

1-) Defining suitable names and using appropriate HTTP Methods

API optimization is not only about performance, but also about readability, sustainability, and correct resource modeling. In this context, the use of appropriate HTTP methods and endpoint naming are the cornerstones of good API design. Here are a few key points:

  • Nouns should be used instead of verbs in endpoint naming. /users, /products, /orders etc.
  • A hierarchical relationship should be established between related resources, as in the following example: /users/123/orders
  • The GET method should never be used to create or modify data. It should only be used to retrieve data, and the endpoint should be idempotent.
  • For creating a new resource, use the POST method; for updating all fields of an existing resource, use the PUT method; and for partially updating data, use the PATCH method.

2-) Prevention of Abuse

DDoS is one of the most well-known examples of abuse. A malicious attacker can send thousands of API requests in a very short time, rapidly exhausting your application server’s thread pools, driving CPU and memory usage to critical levels, and eventually causing the server to become completely unresponsive.

However, similar consequences may also arise from entirely innocent behavior — for instance, when a legitimate customer unintentionally triggers a high volume of simultaneous requests to some of your services.

To mitigate the effects of such high-intensity usage patterns, a well-designed rate limiter implementation can be considered. By enforcing request limits — especially on services that handle complex business logic or intensive computations — you can prevent server overload and maintain system availability.

3-) Limiting Response

Limiting the contents of API response bodies and returning only the necessary data is considered a best practice. This approach not only minimizes the risk of data breaches caused by exposing unnecessary information but also reduces the amount of data that services need to process, resulting in improved performance.

However, there are exceptional cases where returning large payloads through APIs becomes inevitable. For instance, in an e-commerce application’s admin panel, sellers may want to view detailed reports of their sales for the last quarter of the year. Serving the entire dataset in a single response can severely degrade the performance of the service due to the size of the data.

In such cases, one of the most effective solutions is to paginate the data — delivering it to the user in chunks. The client then sends the same request multiple times to retrieve each subsequent block of data. Another alternative is to send the full dataset in a compressed format, such as a GZIP archive, rather than in raw form. This significantly reduces the size of the data being transferred and lowers bandwidth usage across the network. In the table below, you can observe the comparative differences between the three most commonly used algorithms.

Press enter or click to view image in full size
Comparison of Common Compression Algorithm

4-) Caching

Press enter or click to view image in full size
A Simple Cache Flow

Caching is one of the most important techniques for improving API performance. In essence, it is based on the principle of storing frequently accessed data or computation results in memory for a specific key value, allowing it to be retrieved or computed quickly without the need for re-accessing or re-computing. Thanks to caching:

  • Backend load can be minimized.
  • User satisfaction is increased by reducing delays in service calls.
  • System resources such as memory and network bandwidth are consumed less, contributing to system reliability.

I’d like to talk about two concepts that you may frequently encounter in caching:

Cache Hits: If the requested data is found in the cache, it is referred to as a cache hit.

Cache Miss: If the data is not found in the cache, it is termed a cache miss. In this case, as you can see in the flow diagram above, the backend server is accessed to find the desired data, and the result is added to the cache.

Caching can be widely used in various components of large-scale applications. Let’s take a look at the types of caching…

1.In-Memory Caching

This type of caching is also known as server-side caching. It is based on the fundamental principle of storing data in memory, rather than on a hard disk or a database, and accessing it from there. Since the speed of reading data from memory is much higher than from a hard disk, the performance is improved by minimizing the latency time. Products such as Redis, Memcache, and Amazon ElastiCache are very common caching implementations.

When you want to create a cache, there are a few things to keep in mind:

Get Yunus Alkan’s stories in your inbox

Join Medium for free to get updates from this writer.

TTL Duration(Time To Live): This determines how long data will remain in the cache. If your data is not updated very often, you can set the TTL period to be long. However, it is important to set the TTL period to be short for data that is updated frequently. Otherwise, users may continue to see outdated data. Setting the TTL correctly according to the data’s need for updating is one of the most critical issues.

Eviction Policy: When data is stored in cache, it is typically not stored in memory indefinitely. Since memory capacity is limited, data stored here should be purged according to policies set to maintain a balance of memory load. The most commonly used policies are as follows:

  • LRU (Least Recently Used): Removes the data that was used the longest time ago from the cache.
  • LFU (Least Frequently Used): Removes the least frequently used data from the cache.
  • FIFO (First-In, First-Out): Removes the data that was first entered into the cache

Key Design: It is particularly important to assign unique key values in caching implementations where data is stored in key-value pairs. Otherwise, there is a high probability that you will experience conflicts and data inconsistencies. An example key value will be customer:123456:order:1

2. Database Optimization

Effective database optimization plays a critical role in improving API performance. These optimizations impact not only the queries used to retrieve data but also the communication between the database and the API. Indexes are highly beneficial for accelerating data access within tables. In particular, creating single or composite indexes on columns that are frequently used in the WHERE clause is essential. However, using indexes also introduces overhead. Since each newly inserted record requires the index to be updated, this comes at a cost in terms of both storage size and CPU usage. Therefore, for tables with high insert rates, index design should be carefully planned. Over time, as the number of indexed columns increases, insert operations may experience a significant degradation in performance.

When an API accesses the database, it must first establish a connection — a process that carries its own cost. If incoming API requests increase rapidly, opening new database connections for each concurrent request can place a heavy load on the database. To mitigate this, a connection pool should be utilized. A predefined and limited number of connections are kept alive in the pool and returned to it once the task is completed. This eliminates the overhead of creating a new connection for each request. Properly configuring the connection pool — including the maximum number of connections and the idle timeout — is crucial. If the number of available connections is insufficient, threads may end up waiting for a connection to become available, potentially leading to application downtime.

The number of records returned by a database query should always be limited using pagination. Retrieving millions of records in a single request is highly risky — both for the database and the API layer. Additionally, selecting all columns using a wildcard (SELECT *) when it is not necessary leads to excessive memory usage and unnecessary network bandwidth consumption. Only the required fields should be explicitly included in the query results.

In high-write workloads, read performance may degrade over time. A common and effective solution to this problem is to separate read and write operations across different databases. While write operations are handled by the master database, the data is replicated — with a slight delay — to one or more read-only replicas. This ensures that read operations are isolated from write-intensive processes. However, the main drawback of this architecture is the potential for eventual consistency issues due to replication delays, especially for critical data.

3. HTTP Caching

The goal of this type of caching is to store HTML page content (such as images, URLs, CSS and JavaScript files) in the user’s browser on the first request to a web site, and then retrieve this content from the local cache on subsequent requests. In this way, both faster response time and increased user satisfaction, as well as a decrease in server bandwidth usage, can be achieved. Caching is not limited to the browser memory alone. An additional server called a Proxy Cache is located between the client and the server and works like CDN. In this case, the cached data is not only specific to the user, but can also be opened to all public users.

The HTTP Caching process is accomplished through directives in the request or response headers. Let’s take a look a simple response header:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=3600, must-revalidate
ETag: "a7c3d-5e1f-abc123"
Last-Modified: Wed, 07 Aug 2025 18:30:00 GMT
Expires: Wed, 07 Aug 2025 19:30:00 GMT

Cache-Control: In this section, we indicate whether the cached data is private or open to everyone, for how many seconds it will be valid, and whether it should be revalidated from the server after the time expires.

ETag: The name of the header is derived from the combination of words ‘entity’ and ‘tag’. It represents a unique value for the resource to be cached by the backend server. If this resource value changes at any time in the future, it indicates that the original resource has changed and the cache information needs to be flushed and refilled.

Expires: As the name suggests, this header information also specifies when the cache information will expire.

4-) Using Asynchronous Processing

One of the best practices is to complete processes that do not need to be completed in a very short time asynchronously. For example, since it is not necessary to send a post-order notification email immediately in an e-commerce application, it can be implemented asynchronously using message queue systems such as Kafka and RabbitMQ. For a solid example of asynchronous processing implementation, consider reviewing this article. The advantages of asynchronous processes can be listed as follows:

  • By moving time-intensive operations to an asynchronous structure, the services that perform the main task will work more efficiently.
  • Since email delivery is performed asynchronously, various delays or sudden interruptions in the email delivery service will not affect the order creation process.
  • The greatest advantage of using message queues is that, in the event of an error during the operation, retry mechanisms allow for repeated attempts at specific intervals without data loss.

While asynchronous processing offers significant advantages, it also comes with challenges that, if not properly managed, can cause more harm than benefit. A few key considerations include:

  • Difficulty in Error Handling: Due to the non-linear flow of code, handling errors within deeply nested asynchronous calls can become increasingly complex.
  • Decreased Code Readability: As the number of nested asynchronous methods that call each other increases, code readability and maintainability can suffer significantly. This situation is commonly referred to as “Callback Hell” or the “Pyramid of Doom.”
  • Resource Management: Although asynchronous processes may seem to run quietly in the background, they can significantly increase memory usage — especially when multiple asynchronous operations are initiated simultaneously — which may negatively impact the performance of the main thread. Additionally, concurrent asynchronous processes accessing the same resource (such as a database record with the same ID) can lead to race conditions and result in data inconsistencies.
  • Debugging and Testing Challenges: The non-sequential nature of asynchronous code makes detecting and fixing bugs more difficult compared to synchronous implementations. Similarly, crafting and executing reliable test scenarios in such environments requires extra care and attention.
  • Thread Pool Management: When implementing asynchronous calls using custom-created threads, it is often more efficient to utilize a customized thread pool mechanism. Creating a new thread for each individual process is highly resource-intensive. Instead, pre-initialized threads can be reused through pooling, thereby avoiding the overhead of thread creation and enabling more efficient utilization of system resources such as memory and CPU. However, thread pool configuration must be handled with great care. If the pool is not properly sized, issues such as thread starvation, resource contention, or even memory overflows (e.g., OutOfMemory errors) may occur. Therefore, parameters like core pool size, maximum pool size, queue capacity, and thread keep-alive time should be carefully tuned based on whether the workload is CPU-bound or I/O-bound.

5. Content Delivery Networks (CDNs)

It can be very costly for application servers to prepare and serve static content, such as URLs, HTML content, and images, for each request from an API request. This is especially true when the size of the visual content is large, in which case the bandwidth consumed by the application will increase dramatically. To address this issue, CDNs (Content Delivery Networks) have been developed.

CDNs, which operate with a distributed server cluster architecture, include a caching implementation that purges the stored data at regular intervals. When we return the addresses of large volumes of data stored on the CDN in service responses, this content will be delivered to the user making the request from the CDN server closest to them. This improves latency and ensures efficient use of our application server’s memory and network bandwidth.

A Typical CDN Cluster

Conclusion

Optimizing APIs is not merely a matter of improving speed — it is about ensuring reliability, scalability, and a seamless experience for both users and systems. From adopting proper endpoint naming conventions and safeguarding services against abuse, to limiting response payloads, implementing effective caching strategies, and leveraging asynchronous processing, each technique plays a vital role in building resilient APIs.

By approaching API optimization as an ongoing process rather than a one-time effort, organizations can reduce operational costs, enhance customer satisfaction, and create a robust technological foundation that grows with their business needs. In a world where milliseconds matter, well-optimized APIs are no longer a competitive advantage — they are a necessity.

--