After running hundreds of performance tests across different applications, you start seeing the same problems over and over. About 80% of performance issues fall into the same 6 categories. Knowing them helps you design better tests and diagnose results faster.
The number one cause of performance problems. A query that takes 50ms on your development database with 1,000 rows takes 5 seconds on production with 10 million rows. Missing indexes, unoptimized joins, and the dreaded N+1 query pattern are the usual suspects.
The N+1 problem: your code fetches 100 orders, then for each order, makes a separate query to get the customer name. That is 101 database queries instead of 1 query with a JOIN. Under load with 50 users doing this simultaneously, that is 5,050 queries hitting your database at once.
-- Bad: N+1 queries (101 total for 100 orders)
SELECT * FROM orders WHERE status = 'pending'; -- 1 query
SELECT name FROM customers WHERE id = 1; -- repeated
SELECT name FROM customers WHERE id = 2; -- 100 times
-- ...
-- Good: 1 query with JOIN
SELECT o.*, c.name
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.status = 'pending';Memory leaks are silent killers. The application starts using 500MB. It works fine. After 8 hours, it uses 2GB. After 24 hours, it runs out of memory and crashes. The restart fixes it -- temporarily. Until 24 hours later when it crashes again. Common causes: event listeners not being removed, large objects held in closures, cache without eviction policy, growing arrays that are never cleared.
Most applications use connection pools for databases, HTTP clients, and message queues. A pool of 50 database connections means only 50 requests can talk to the database simultaneously. The 51st request waits. If requests hold connections for too long (slow queries, forgotten connection closes), the pool fills up and everything grinds to a halt.
Web servers have a limited number of worker threads. When all workers are busy handling requests, new requests queue up. If a few requests take too long (calling a slow external API, processing large files), they block workers and cause a cascading delay for every other request. This is why one slow endpoint can bring down an entire application.
Network issues manifest as high latency, bandwidth saturation, or excessive round trips. A page that makes 200 HTTP requests to load (images, CSS, JS, API calls) suffers from "chatty" communication. Each round trip adds latency. Under load, the network becomes a bottleneck before the server does.
Sometimes the bottleneck is the code itself. Unnecessary loops, blocking operations in async code, string concatenation in tight loops, synchronous file reads, or unoptimized algorithms. A function that takes 100ms is fine when called once. When called 10,000 times per request under load, it is the bottleneck.
| Problem | Symptom | How to Detect | Typical Fix |
|---|---|---|---|
| Slow queries | High response time, DB CPU spikes | APM, slow query log | Add indexes, optimize queries, add caching |
| Memory leaks | Gradual memory growth, eventual OOM crash | Soak test + memory monitoring | Fix leak source, add memory limits |
| Connection pool exhaustion | Sudden timeout errors | Load test + pool metrics | Increase pool size, fix connection leaks |
| Thread exhaustion | Request queue growing, timeouts | Load test + thread pool metrics | Async processing, increase workers |
| Network bottleneck | High latency, slow page loads | Network analysis, waterfall charts | CDN, reduce requests, compression |
| Inefficient code | High CPU, consistent slow response | Profiling under load | Optimize algorithms, add caching |
Q: What are the most common performance issues you have found in your testing experience?
A: The top three are: (1) Slow database queries -- missing indexes and N+1 patterns are the most common cause of poor performance under load. I always check query execution plans and database metrics first. (2) Connection pool exhaustion -- applications run out of database or HTTP connections under load because connections are not being released properly. I monitor pool metrics during every load test. (3) Memory leaks -- only visible during soak tests, where memory grows gradually over hours until the application crashes. I always run at least a 4-hour soak test before major releases. The common theme: all three are invisible in functional testing with a single user.
Key Point: Six common problems: slow queries, memory leaks, connection pool exhaustion, thread exhaustion, network bottlenecks, and inefficient code. All invisible in functional testing. Performance testing finds them before production does.
Key Point: Six categories cover 80% of performance problems -- all are invisible in functional tests and only appear under load