Jump to Category
| ️ Caching Strategies | Database Optimization |
| ⚙️ Application & Code-Level Tuning | ⚡ Concurrency & Asynchronous Processing |
| ️ Network & Protocol Optimization | ️ Architectural Patterns for Performance |
Caching Strategies
1. Compare Cache-Aside, Write-Through, and Write-Back caching strategies.
- Cache-Aside (Lazy Loading): The application logic is responsible for managing the cache. It first checks the cache; if there’s a miss, it reads from the database and then writes the result to the cache. This is the most common pattern, but it results in a slightly higher latency on a cache miss.
- Write-Through: The application writes directly to the cache. The cache itself is then responsible for synchronously writing the data to the database. This ensures data consistency between the cache and DB, but it introduces write latency as you have to write to two systems.
- Write-Back (or Write-Behind): The application writes only to the cache. The cache acknowledges the write immediately and then asynchronously writes the data to the database after a delay. This provides the lowest write latency but risks data loss if the cache fails before the data is persisted.
2. What is the “thundering herd” problem and how can you mitigate it?
The **thundering herd** problem occurs when a high-traffic cached item expires, causing a massive, simultaneous rush of requests from multiple processes or servers to regenerate that same piece of data by querying the backend database. This can overwhelm the database.
Mitigation Strategies:
- Mutex/Locking: The first process to experience a cache miss acquires a lock. It regenerates the data and populates the cache while other processes wait for the lock to be released and then read the newly populated cache.
- Stale-while-revalidate: Serve the stale (expired) cache data to most users while a single background process is triggered to regenerate the new value.
3. What are some effective cache invalidation strategies?
- Time-To-Live (TTL): The simplest strategy. Data is automatically evicted from the cache after a set period. This is easy but can result in serving stale data until the TTL expires.
- Explicit Invalidation: When the source data is updated, the application explicitly sends a command to delete the corresponding key from the cache. This keeps data fresh but adds complexity and couples the cache logic with the data-writing logic.
- Event-Driven Invalidation: The service that updates the data publishes an event (e.g., `ProductUpdated`). A separate service listens for these events and is responsible for invalidating the appropriate cache keys. This decouples the cache management from the primary service.
4. Differentiate between an in-memory cache and a distributed cache.
- In-Memory Cache: The cache is stored in the application’s own memory space (e.g., a hash map or an LRU cache library). It is extremely fast but is local to a single application instance and is lost on restart.
- Distributed Cache: The cache is an external service (like Redis or Memcached) that is shared by multiple application instances. It provides a consistent cache across a scaled-out service but introduces network latency for every cache lookup.
Database Optimization
5. What is the N+1 query problem and how do you solve it?
The N+1 problem occurs when an application makes one query to fetch a list of N parent items, and then makes N subsequent queries inside a loop to fetch a related child item for each parent. This results in N+1 database round trips, which is highly inefficient.
Solution: The solution is to use **eager loading**. Instead of loading data lazily inside a loop, you instruct your ORM or data layer to fetch all the necessary related data in a constant number of queries (usually just two) before the loop begins. In SQL, this is typically done with a `JOIN` or a second query using `WHERE IN (…)`.
Read a guide on the N+1 problem.6. Why is database connection pooling crucial for a high-traffic backend?
Establishing a new database connection is a very resource-intensive operation, involving network handshakes, authentication, and memory allocation on the database server. If a high-traffic application created a new connection for every request, it would quickly overwhelm the database.
A **connection pool** maintains a “pool” of open, ready-to-use database connections. When the application needs to run a query, it borrows a connection from the pool and returns it when done. This avoids the high cost of connection setup and teardown for every request, dramatically improving performance and scalability.
Learn more about Connection Pooling.7. What is a covering index?
A covering index is an index that contains all the columns required to satisfy a query, including those in the `WHERE` clause and the `SELECT` list. When a covering index can be used, the database can answer the query by only reading the index data structure, without having to perform an additional lookup to the main table data. This significantly reduces I/O and improves query speed.
8. When would you consider using read replicas?
You would use read replicas to scale out a read-heavy application. A read replica is a live, read-only copy of your primary database. You can direct all write traffic to the primary database and distribute read traffic across one or more read replicas.
This is effective for workloads where the volume of reads is much higher than writes, as it offloads the read traffic from the primary instance, freeing it up to handle writes more efficiently. It’s important to account for potential replication lag (eventual consistency).
9. How do you analyze a slow query?
The first step is to use the database’s query execution plan tool (e.g., `EXPLAIN ANALYZE` in PostgreSQL). This shows you exactly how the database optimizer plans to execute the query. Key things to look for are:
- Full Table Scans: The database is reading the entire table instead of using an index.
- Incorrect Index Usage: An inefficient index is being chosen.
- Expensive Joins: Inefficient join algorithms (like a nested loop on a large table).
- Large Discrepancies: A big difference between the planner’s estimated row count and the actual rows returned can indicate stale statistics.
Based on this analysis, the solution is typically to add or restructure indexes, or rewrite the query.
Application & Code-Level Tuning
10. What is the impact of garbage collection (GC) on application latency?
Garbage collection can introduce significant latency, especially “stop-the-world” GC pauses where the entire application is frozen while the GC reclaims memory. In a low-latency backend, these pauses can cause missed SLAs and request timeouts.
Optimizing for GC involves reducing the rate of memory allocation. This means avoiding creating unnecessary objects in hot code paths, using object pooling for expensive objects, and choosing efficient data structures. Modern GCs (like ZGC or Shenandoah in the JVM) are designed to minimize pause times but cannot eliminate the overhead completely.
11. Compare the performance characteristics of JSON and Protocol Buffers (Protobuf).
- JSON: A text-based format. It is human-readable and widely supported. However, it is verbose and requires more CPU time to parse.
- Protobuf: A binary serialization format. It is much smaller on the wire and significantly faster to serialize and deserialize because it uses a pre-defined schema.
For internal, performance-critical service-to-service communication, Protobuf (often used with gRPC) is a superior choice. For public-facing APIs where interoperability and readability are key, JSON is still the standard.
12. What is object pooling?
Object pooling is a performance optimization pattern where you reuse objects instead of creating new ones. It’s used for objects that are expensive to create, such as database connections, threads, or large memory buffers. A “pool” maintains a set of initialized objects. When the application needs an object, it borrows one from the pool. When it’s finished, it returns the object to the pool instead of destroying it. This reduces the overhead of object creation and garbage collection.
13. How would you use a profiler to identify a performance bottleneck in your code?
A profiler is a tool that analyzes your application’s runtime performance. The process is:
- Run the application under the profiler, which samples its execution.
- Analyze the output. A **CPU profiler** will show which functions or methods are consuming the most CPU time. This is often visualized as a **flame graph**.
- A **memory profiler** will show which objects are being allocated most frequently and where memory leaks might be occurring.
- Based on this data, you can focus your optimization efforts on the actual “hot spots” in the code rather than guessing.
14. What is the impact of logging on performance?
Logging, especially synchronous logging to disk or a network service, can be a significant performance bottleneck. Each log statement can involve I/O operations that block the main application thread.
To mitigate this, use **asynchronous logging**. The application writes log messages to an in-memory buffer, and a separate background thread is responsible for formatting these messages and writing them to their final destination (e.g., a file or a logging service). This decouples the application’s request-processing threads from the I/O latency of logging.
Concurrency & Asynchronous Processing
15. Differentiate between a CPU-bound and an I/O-bound task. How does this affect your choice of concurrency model?
- I/O-Bound: A task that spends most of its time waiting for I/O operations to complete (e.g., waiting for a network request or a database query). The CPU is mostly idle.
- CPU-Bound: A task that spends most of its time performing computations (e.g., image processing, complex calculations). The CPU is fully utilized.
This choice is critical. An **event loop / async-await model** (like in Node.js) is excellent for I/O-bound workloads, as a single thread can handle thousands of concurrent operations by not blocking on I/O. For CPU-bound workloads, a **multi-threaded** or **multi-process** model is necessary to leverage multiple CPU cores and achieve true parallelism.
Read about Blocking vs. Non-Blocking I/O.16. How can message queues be used to improve application performance and responsiveness?
Message queues (like RabbitMQ or SQS) allow you to offload long-running or non-critical tasks to be processed asynchronously by background workers. When a client makes a request that involves a slow operation (e.g., generating a report), the API can simply place a “job” message onto a queue and immediately return a success response to the client.
This makes the primary API extremely fast and responsive. It also improves reliability, as the message queue can retry failed jobs, and it helps with scalability by allowing you to scale the number of background workers independently from the web servers.
17. Explain the event loop model used by runtimes like Node.js.
The event loop is a programming construct that allows a single thread to perform non-blocking I/O operations. The model is:
- The event loop continuously checks a “task queue” for new tasks (events).
- When it finds a task, it executes its associated callback function.
- If this callback initiates an asynchronous I/O operation, it provides a callback for that operation and hands the operation off to the underlying system (e.g., the OS kernel). The event loop does *not* wait.
- The loop immediately moves on to process the next task in the queue.
- When the I/O operation completes, the system places a new event with the result onto the task queue, which the loop will eventually pick up and execute.
This allows a single thread to handle thousands of concurrent connections efficiently.
Network & Protocol Optimization
18. How does HTTP/2 improve performance over HTTP/1.1?
HTTP/2 introduced several key features to address the limitations of HTTP/1.1:
- Multiplexing: Allows multiple requests and responses to be sent in parallel over a single TCP connection, eliminating the “head-of-line blocking” problem.
- Header Compression (HPACK): Compresses redundant HTTP header data, reducing overhead.
- Server Push: Allows the server to proactively send resources to the client that it knows will be needed, without the client having to explicitly request them.
- Binary Protocol: More efficient and less error-prone to parse than the text-based HTTP/1.1.
19. What is the benefit of using gRPC for inter-service communication?
gRPC is a high-performance RPC (Remote Procedure Call) framework. Its primary benefits for backend performance are:
- Efficiency: It uses Protocol Buffers (Protobuf) for serialization, which is a compact and fast binary format.
- Performance: It operates over HTTP/2, taking advantage of features like multiplexing and streaming.
- Strict Contracts: Services are defined in a `.proto` file, which allows for generating strongly-typed client and server code, reducing integration errors.
20. What is the purpose of the `Keep-Alive` HTTP header?
The `Keep-Alive` header (which is the default behavior in HTTP/1.1 and beyond) allows a single TCP connection to be reused for multiple HTTP requests and responses, rather than opening a new connection for every single request. This significantly reduces latency by avoiding the overhead of the TCP handshake (SYN/SYN-ACK/ACK) for every request.
Architectural Patterns for Performance
21. What is the CQRS (Command Query Responsibility Segregation) pattern?
CQRS is an architectural pattern that separates the models used for updating data (Commands) from the models used for reading data (Queries). This allows you to optimize the write path and the read path independently.
For performance, this is powerful because you can create highly denormalized, tailored “read models” that are optimized for specific queries, avoiding complex `JOIN`s and aggregations at read time. The read models are then updated asynchronously in response to events from the write side. This leads to extremely fast query performance.
Read Martin Fowler’s article on CQRS.22. What is the Strangler Fig pattern?
The Strangler Fig pattern is an approach for incrementally modernizing a legacy monolithic application. You place a proxy or facade in front of the monolith. As you build new microservices to replace parts of the monolith’s functionality, you update the proxy to route traffic for those specific features to the new services. Over time, the new system “strangles” the old one until the monolith can be decommissioned. This is a performance and reliability strategy, as it allows for a gradual, lower-risk migration.
23. How does a CDN improve backend performance?
A Content Delivery Network (CDN) is a distributed network of servers that caches content close to end users. While primarily used for static assets, it can significantly improve backend performance by:
- Caching API Responses: For public, cacheable GET requests, the CDN can serve the response directly from an edge location, completely avoiding a hit to your backend.
- Reducing Latency: For dynamic requests, the CDN terminates the user’s connection at a nearby edge location and then uses its optimized, persistent connections over the network backbone to communicate with your origin server, reducing round-trip time.
24. Differentiate between horizontal and vertical scaling.
- Vertical Scaling (Scaling Up): Increasing the resources of a single server, such as adding more CPU, RAM, or faster storage. There is a physical limit to how much you can scale up, and it can become very expensive.
- Horizontal Scaling (Scaling Out): Adding more servers to a pool of resources. Your application must be designed to be stateless or to handle distributed state to take advantage of this. This is the standard approach for modern, cloud-native applications as it is more flexible and cost-effective.
25. What is a flame graph and how is it used to diagnose CPU performance issues?
A flame graph is a visualization of profiled software, showing the call stack hierarchy. The x-axis represents the percentage of time a function was on the CPU, and the y-axis represents the depth of the call stack. The width of a function’s bar on the graph is proportional to the time it spent on the CPU.
By looking for wide bars at the top of the stack, you can quickly and visually identify the most “hot” or frequently executed code paths that are consuming the most CPU time, allowing you to focus your optimization efforts where they will have the most impact.
See Brendan Gregg’s page on Flame Graphs.


