Methodology9 min

Performance Anti-patterns: mistakes you must avoid

Learn the most common mistakes that destroy performance and how to avoid them before they cause problems.

Some development patterns seem harmless or even like good ideas, but they devastate performance when the system scales. These are anti-patterns — practices that work in development but fail in production.

This article presents the most common anti-patterns and how to avoid them.

Code that works on the developer's laptop doesn't necessarily work with 10,000 simultaneous users.

Code Anti-patterns

1. N+1 Queries

The problem:

// Fetches 100 orders, then 100 queries for customers
const orders = await Order.findAll();
for (const order of orders) {
    order.customer = await Customer.findById(order.customerId);
}
// 101 queries to the database

The solution:

// One query with JOIN
const orders = await Order.findAll({
    include: [Customer]
});
// 1 query to the database

2. Serialization in Loop

The problem:

const results = [];
for (const item of items) {
    results.push(JSON.stringify(item)); // Serializes one by one
}

The solution:

const results = JSON.stringify(items); // Serializes everything at once

3. String Concatenation in Loop

The problem:

String result = "";
for (String s : strings) {
    result += s; // Creates new string each iteration
}

The solution:

StringBuilder sb = new StringBuilder();
for (String s : strings) {
    sb.append(s);
}
String result = sb.toString();

4. Unnecessary Synchronous Calls

The problem:

const user = await getUser(id);
const orders = await getOrders(id);
const recommendations = await getRecommendations(id);
// 3 sequential calls = sum of latencies

The solution:

const [user, orders, recommendations] = await Promise.all([
    getUser(id),
    getOrders(id),
    getRecommendations(id)
]);
// 3 parallel calls = maximum of latencies

5. Poorly Constructed Regex

The problem:

// Regex with exponential backtracking
const regex = /^(a+)+$/;
regex.test("aaaaaaaaaaaaaaaaaaaaaaaaaaaaab"); // Hangs!

The solution:

// Regex without problematic backtracking
const regex = /^a+$/;

Architecture Anti-patterns

6. Database as Queue

The problem:

-- Constant polling
SELECT * FROM jobs WHERE status = 'pending' LIMIT 1;
UPDATE jobs SET status = 'processing' WHERE id = ?;

Why it's bad:

  • Constant polling consumes resources
  • Contention on updates
  • Doesn't scale

The solution: Use a real queue: RabbitMQ, SQS, Kafka.

7. Unbounded Cache

The problem:

const cache = new Map();
function get(key) {
    if (!cache.has(key)) {
        cache.set(key, fetchFromDB(key));
    }
    return cache.get(key);
}
// Cache grows infinitely

The solution:

// Use LRU cache with limit
const cache = new LRUCache({ max: 1000 });

8. Synchronous Logging in Critical Path

The problem:

async function handleRequest(req) {
    await logger.log('Request received'); // Waits for I/O
    const result = process(req);
    await logger.log('Request processed'); // Waits for I/O again
    return result;
}

The solution:

async function handleRequest(req) {
    logger.log('Request received'); // Async, fire-and-forget
    const result = process(req);
    logger.log('Request processed');
    return result;
}

9. Long Transactions

The problem:

await db.beginTransaction();
const user = await db.getUser(id);
await externalAPI.validate(user); // External call INSIDE transaction
await db.updateUser(user);
await db.commit();
// Transaction holds lock for duration of external call

The solution:

const user = await db.getUser(id);
await externalAPI.validate(user); // OUTSIDE transaction

await db.beginTransaction();
await db.updateUser(user);
await db.commit();

10. Retry without Backoff

The problem:

while (!success) {
    success = await tryOperation();
    // Immediate retry in tight loop
}

The solution:

let delay = 100;
while (!success && attempts < maxAttempts) {
    success = await tryOperation();
    if (!success) {
        await sleep(delay);
        delay *= 2; // Exponential backoff
    }
}

Infrastructure Anti-patterns

11. Default Configurations in Production

The problem:

# Connection pool default: 10 connections
# Timeout default: 30 seconds
# Buffer default: 8KB

Why it's bad: Defaults are for development, not production.

The solution: Tune each configuration for your workload:

pool_size: 50
timeout: 5s
buffer: 64KB

12. DEBUG Logs in Production

The problem:

DEBUG: Entering function processOrder
DEBUG: Parameter validation passed
DEBUG: Calling database
DEBUG: Query took 5ms
DEBUG: Processing result...
// Millions of lines per hour

The solution:

# Production: ERROR and WARNING only
log_level: WARN

13. Health Check that Does Real Work

The problem:

app.get('/health', async (req, res) => {
    const dbCheck = await db.query('SELECT 1');
    const cacheCheck = await cache.ping();
    const apiCheck = await externalAPI.status();
    res.json({ status: 'healthy' });
});
// Health check consumes resources and can fail due to dependencies

The solution:

// Liveness: only checks if app is running
app.get('/health/live', (req, res) => res.json({ status: 'ok' }));

// Readiness: checks dependencies (less frequent)
app.get('/health/ready', async (req, res) => {
    // Lighter checks with short timeout
});

Testing Anti-patterns

14. Testing with Unrealistic Data

The problem:

// Test with 10 records
// Production has 10 million

The solution: Test with similar volume to production or realistic projections.

15. Ignoring Warm-up

The problem:

5 minute test:
- 2 min warm-up (ignored)
- 3 min steady state
= Insufficient and distorted data

The solution: Exclude warm-up from metrics and have long enough steady state.

16. Measuring Only Average

The problem:

Average latency: 50ms ✓
p99 latency: 5000ms ✗ (hidden by average)

The solution: Always measure percentiles: p50, p90, p95, p99.

How to Avoid Anti-patterns

1. Performance-focused code review

Checklist:

  • Are there loops with I/O inside?
  • Can queries be batched?
  • Can operations be parallel?
  • Does cache have a limit?
  • Are logs appropriate for production?

2. Regular load testing

Find problems before users find them.

3. Production profiling

Use APM to identify real hotspots, not assumed ones.

4. Performance culture

Performance isn't one team's task. It's everyone's responsibility.

Conclusion

Anti-patterns are traps waiting to be triggered when your system scales. Most:

  • Work well in development
  • Fail spectacularly in production
  • Are hard to identify without proper testing

To avoid them:

  1. Know the common anti-patterns
  2. Review code with a performance mindset
  3. Test with realistic load
  4. Monitor production behavior

The best time to avoid an anti-pattern is before writing it. The second best is now.

anti-patternsbest practicesmistakesoptimization

Want to understand your platform's limits?

Contact us for a performance assessment.

Contact Us