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:
- Know the common anti-patterns
- Review code with a performance mindset
- Test with realistic load
- Monitor production behavior
The best time to avoid an anti-pattern is before writing it. The second best is now.