| name | performance-optimization |
| description | Systematic approach to identifying and resolving performance bottlenecks in software systems. Use when application response times exceed SLAs, resource usage is high, before scaling infrastructure, during performance testing, or when users report slowness. |
Performance Optimization Skill
Core Principle
Measure first, optimize second. Never guess at performance.
Performance optimization without measurement is guesswork. Every optimization must:
- Start with profiling — Identify actual bottlenecks, not assumed ones
- Establish baselines — Know current performance before changing anything
- Measure impact — Verify optimizations actually improve performance
- Avoid premature optimization — Optimize based on real-world needs, not theoretical concerns
- Consider trade-offs — Every optimization has costs (complexity, maintainability, development time)
When to Optimize
Good Times to Optimize
✅ SLA violations — Response times consistently exceed service level agreements
✅ High resource usage — CPU, memory, or I/O consistently at capacity
✅ Before scaling — Optimize code before throwing more hardware at the problem
✅ User complaints — Real users reporting slowness or timeouts
✅ Performance testing — During regular performance validation cycles
✅ After profiling — When bottlenecks are identified and measured
Bad Times to Optimize
❌ Without profiling — Don't optimize without knowing where the bottleneck is
❌ Premature optimization — Don't optimize before you have performance problems
❌ During feature development — Build it first, optimize later
❌ Without tests — Can't verify correctness after optimization
❌ Based on hunches — "I think this is slow" without measurement
Performance Profiling
Profiling Categories
1. CPU Profiling
What it measures: Where CPU time is spent in your code
When to use:
- High CPU usage
- Slow computation or algorithms
- Long request processing times
Tools by language:
Python:
python -m cProfile -s cumulative script.py
py-spy record -o profile.svg -- python script.py
kernprof -l -v script.py
JavaScript/Node.js:
node --inspect script.js
clinic doctor -- node script.js
clinic flame -- node script.js
node --prof script.js
node --prof-process isolate-*.log
Go:
go test -cpuprofile=cpu.prof -bench=.
go tool pprof cpu.prof
import _ "net/http/pprof"
Rust:
cargo install flamegraph
cargo flamegraph
perf record --call-graph=dwarf ./target/release/app
perf report
Java:
java -agentpath:/path/to/libasyncProfiler.so=start,file=profile.html -jar app.jar
2. Memory Profiling
What it measures: Memory allocation, usage, and leaks
When to use:
- High memory consumption
- Out of memory errors
- Memory leaks (growing memory over time)
- Garbage collection pressure
Tools by language:
Python:
pip install memory_profiler
python -m memory_profiler script.py
pip install memray
memray run script.py
memray flamegraph output.bin
import objgraph
objgraph.show_most_common_types()
JavaScript/Node.js:
node --inspect script.js
clinic heapprofiler -- node script.js
npm install @airbnb/node-memwatch
Go:
go test -memprofile=mem.prof -bench=.
go tool pprof mem.prof
import _ "net/http/pprof"
curl http://localhost:6060/debug/pprof/heap > heap.prof
go tool pprof heap.prof
Rust:
valgrind --tool=massif ./target/release/app
ms_print massif.out.*
heaptrack ./target/release/app
3. I/O Profiling
What it measures: Disk and network I/O operations
When to use:
- Slow file operations
- Network latency
- Database query performance
- API call delays
Tools:
strace -c python script.py
strace -T python script.py
sudo iotop -o
sudo tcpdump -i any -w capture.pcap
wireshark capture.pcap
4. Database Profiling
What it measures: Query performance, connection usage, lock contention
PostgreSQL:
ALTER SYSTEM SET log_min_duration_statement = 100;
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'user@example.com';
SELECT query, mean_exec_time, calls
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
ORDER BY idx_scan;
MySQL:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.1;
EXPLAIN SELECT * FROM users WHERE email = 'user@example.com';
SHOW FULL PROCESSLIST;
MongoDB:
db.setProfilingLevel(1, { slowms: 100 });
db.users.find({ email: 'user@example.com' }).explain("executionStats");
db.system.profile.find().sort({ ts: -1 }).limit(10);
5. Frontend Profiling
What it measures: Rendering, JavaScript execution, bundle size, network requests
Tools:
Chrome DevTools:
Web Vitals:
import { getCLS, getFID, getLCP } from 'web-vitals';
getCLS(console.log);
getFID(console.log);
getLCP(console.log);
Bundle analysis:
npm install --save-dev webpack-bundle-analyzer
npx webpack-bundle-analyzer dist/stats.json
npm install -g source-map-explorer
source-map-explorer bundle.js bundle.js.map
Optimization Techniques by Layer
1. Algorithm and Data Structure Optimization
Before (O(n²) - inefficient):
def find_duplicates(items):
duplicates = []
for i in range(len(items)):
for j in range(i + 1, len(items)):
if items[i] == items[j] and items[i] not in duplicates:
duplicates.append(items[i])
return duplicates
After (O(n) - efficient):
def find_duplicates(items):
seen = set()
duplicates = set()
for item in items:
if item in seen:
duplicates.add(item)
seen.add(item)
return list(duplicates)
Key principle: Choose the right data structure for the problem
- Lists for ordered data, O(n) search
- Sets for uniqueness, O(1) search
- Dicts for key-value lookups, O(1) access
- Heaps for priority queues
- Trees for hierarchical data
2. Database Optimization
Add Missing Indexes
Before (slow full table scan):
SELECT * FROM orders WHERE user_id = 12345 AND status = 'pending';
After (fast index scan):
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
Fix N+1 Query Problem
Before (N+1 queries - extremely slow):
posts = Post.query.all()
for post in posts:
print(f"{post.title} by {post.author.name}")
After (2 queries with join - fast):
posts = Post.query.options(joinedload(Post.author)).all()
for post in posts:
print(f"{post.title} by {post.author.name}")
Batch Operations
Before (slow - many round trips):
for user_id in user_ids:
db.execute("UPDATE users SET active = true WHERE id = %s", (user_id,))
After (fast - single batch operation):
db.execute(
"UPDATE users SET active = true WHERE id = ANY(%s)",
(user_ids,)
)
Connection Pooling
Before (creating connection each time - slow):
def get_user(user_id):
conn = psycopg2.connect(DATABASE_URL)
cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
user = cursor.fetchone()
conn.close()
return user
After (connection pool - fast):
from psycopg2 import pool
connection_pool = pool.SimpleConnectionPool(
minconn=1,
maxconn=20,
dsn=DATABASE_URL
)
def get_user(user_id):
conn = connection_pool.getconn()
try:
cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
return cursor.fetchone()
finally:
connection_pool.putconn(conn)
3. Network Optimization
Compression
Before (large uncompressed responses):
app.get('/api/data', (req, res) => {
res.json(largeDataObject);
});
After (compressed responses - 5x smaller):
const compression = require('compression');
app.use(compression());
app.get('/api/data', (req, res) => {
res.json(largeDataObject);
});
HTTP/2 Server Push
app.get('/', (req, res) => {
res.push('/styles.css', {
response: { 'content-type': 'text/css' }
});
res.push('/script.js', {
response: { 'content-type': 'application/javascript' }
});
res.sendFile('index.html');
});
CDN for Static Assets
Before (serving from origin):
<img src="https://yourserver.com/images/logo.png">
After (CDN - fast, globally distributed):
<img src="https://cdn.yoursite.com/images/logo.png">
4. Frontend Optimization
Code Splitting and Lazy Loading
Before (single large bundle):
import Dashboard from './Dashboard';
import Analytics from './Analytics';
import Settings from './Settings';
function App() {
return (
<Router>
<Route path="/dashboard" component={Dashboard} />
<Route path="/analytics" component={Analytics} />
<Route path="/settings" component={Settings} />
</Router>
);
}
After (lazy loaded routes):
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./Dashboard'));
const Analytics = lazy(() => import('./Analytics'));
const Settings = lazy(() => import('./Settings'));
function App() {
return (
<Router>
<Suspense fallback={<Loading />}>
<Route path="/dashboard" component={Dashboard} />
<Route path="/analytics" component={Analytics} />
<Route path="/settings" component={Settings} />
</Suspense>
</Router>
);
}
Memoization
Before (re-rendering unnecessarily):
function ProductList({ products, filters }) {
const filteredProducts = products.filter(p =>
p.category === filters.category && p.price <= filters.maxPrice
);
return <div>{filteredProducts.map(renderProduct)}</div>;
}
After (memoized calculation):
import { useMemo } from 'react';
function ProductList({ products, filters }) {
const filteredProducts = useMemo(
() => products.filter(p =>
p.category === filters.category && p.price <= filters.maxPrice
),
[products, filters]
);
return <div>{filteredProducts.map(renderProduct)}</div>;
}
Image Optimization
Before (large unoptimized images):
<img src="photo.jpg" alt="Photo">
After (responsive, optimized images):
<picture>
<source srcset="photo-400.webp 400w, photo-800.webp 800w" type="image/webp">
<source srcset="photo-400.jpg 400w, photo-800.jpg 800w" type="image/jpeg">
<img src="photo-400.jpg" alt="Photo" loading="lazy">
</picture>
5. Caching Strategies
Application-Level Caching
Before (expensive computation every time):
def get_trending_products():
return db.query("""
SELECT product_id, COUNT(*) as views
FROM product_views
WHERE timestamp > NOW() - INTERVAL '24 hours'
GROUP BY product_id
ORDER BY views DESC
LIMIT 10
""").all()
After (cached results):
from functools import lru_cache
from datetime import datetime, timedelta
@lru_cache(maxsize=1)
def get_trending_products_cached(cache_key):
return db.query("""
SELECT product_id, COUNT(*) as views
FROM product_views
WHERE timestamp > NOW() - INTERVAL '24 hours'
GROUP BY product_id
ORDER BY views DESC
LIMIT 10
""").all()
def get_trending_products():
cache_key = datetime.now().replace(second=0, microsecond=0) // timedelta(minutes=5)
return get_trending_products_cached(cache_key)
Redis Caching
import redis
import json
redis_client = redis.Redis(host='localhost', port=6379)
def get_user_profile(user_id):
cache_key = f"user:{user_id}:profile"
cached = redis_client.get(cache_key)
if cached:
return json.loads(cached)
profile = db.query("SELECT * FROM users WHERE id = %s", user_id)
redis_client.setex(cache_key, 3600, json.dumps(profile))
return profile
HTTP Caching Headers
app.get('/api/products/:id', (req, res) => {
const product = getProduct(req.params.id);
res.set('Cache-Control', 'public, max-age=300');
res.set('ETag', product.updated_at);
if (req.headers['if-none-match'] === product.updated_at) {
return res.status(304).end();
}
res.json(product);
});
6. Asynchronous Processing
Before (synchronous - blocks request):
@app.route('/send-report', methods=['POST'])
def send_report():
report_data = generate_report()
send_email(report_data)
return jsonify({"status": "sent"})
After (async with background tasks):
from celery import Celery
celery = Celery('tasks', broker='redis://localhost:6379')
@celery.task
def generate_and_send_report_async(user_id):
report_data = generate_report()
send_email(report_data)
@app.route('/send-report', methods=['POST'])
def send_report():
generate_and_send_report_async.delay(current_user.id)
return jsonify({"status": "processing"})
Performance Optimization Workflow
Step 1: Establish Baseline
Before any optimization, measure current performance:
k6 run --vus 10 --duration 30s load-test.js
Document baseline metrics:
- Response times (p50, p95, p99)
- Throughput (requests/second)
- Error rates
- Resource usage (CPU, memory)
Step 2: Profile to Find Bottlenecks
Use profiling tools to identify where time is spent:
py-spy record -o profile.svg -- python app.py
Analyze the profile:
- What function takes the most time?
- Are there unexpected bottlenecks?
- Is it CPU, memory, I/O, or network bound?
Step 3: Prioritize Optimizations
Create optimization candidates based on:
- Impact — Will this significantly improve performance?
- Effort — How difficult is this to implement?
- Risk — Could this break existing functionality?
Optimization priority matrix:
High Impact + Low Effort = DO FIRST
High Impact + High Effort = Do after quick wins
Low Impact + Low Effort = Do if time permits
Low Impact + High Effort = Don't do
Step 4: Implement One Optimization
Make ONE change at a time:
✅ Good: Add database index for slow query
✅ Good: Implement caching for expensive API call
❌ Bad: Add index + caching + connection pooling + compression
Step 5: Measure Impact
After implementing optimization, measure again:
k6 run --vus 10 --duration 30s load-test.js
Compare to baseline:
- Response time: 250ms → 100ms (60% improvement)
- Throughput: 40/s → 100/s (2.5x improvement)
- Error rate: 5% → 0.5% (10x improvement)
Step 6: Document and Monitor
Document the optimization:
## Performance Optimization: Added Database Index
**Date:** 2026-01-10
**Problem:** User search queries taking 2-5 seconds
**Root Cause:** Full table scan on users table (10M rows)
**Solution:** Added index on email column
**Impact:**
- Response time: 2500ms → 50ms (50x improvement)
- Database CPU: 80% → 20%
**Command:**
CREATE INDEX idx_users_email ON users(email);
**Monitoring:** Set alert if p95 response time > 200ms
Set up monitoring to catch regressions:
if p95_response_time > 200:
send_alert("User search performance degraded")
Common Performance Patterns
1. Caching
When to use: Expensive operations called frequently with same inputs
Strategies:
- In-memory cache: Fast, limited by memory (Redis, Memcached)
- Database query cache: Reduce database load
- CDN cache: Serve static assets from edge locations
- Browser cache: Reduce network requests
Cache invalidation:
cache.set('key', value, expire=3600)
def update_user(user_id, data):
db.update(user_id, data)
cache.delete(f'user:{user_id}')
2. Connection Pooling
When to use: Applications making many database/API connections
Benefits:
- Reuse existing connections
- Avoid connection overhead
- Limit concurrent connections
Implementation:
from sqlalchemy import create_engine, pool
engine = create_engine(
DATABASE_URL,
poolclass=pool.QueuePool,
pool_size=20,
max_overflow=10,
pool_pre_ping=True
)
3. Batch Processing
When to use: Many small operations that can be grouped
Example:
for user in users:
db.execute("INSERT INTO users VALUES (%s, %s)", (user.id, user.name))
db.executemany(
"INSERT INTO users VALUES (%s, %s)",
[(user.id, user.name) for user in users]
)
4. Asynchronous Processing
When to use: Long-running tasks that don't need immediate response
Use cases:
- Email sending
- Report generation
- Image processing
- Data exports
Pattern:
def process_order(order_id):
order = get_order(order_id)
send_confirmation_email(order)
update_inventory(order)
generate_invoice(order)
return {"status": "complete"}
def process_order(order_id):
background_task.delay(order_id)
return {"status": "processing"}
Performance Testing
Load Testing with k6
import http from 'k6/http';
import { check, sleep } from 'k6';
export let options = {
stages: [
{ duration: '1m', target: 10 },
{ duration: '3m', target: 10 },
{ duration: '1m', target: 50 },
{ duration: '3m', target: 50 },
{ duration: '1m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<500'],
http_req_failed: ['rate<0.01'],
},
};
export default function() {
let response = http.get('https://api.example.com/products');
check(response, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}
Run test:
k6 run load-test.js
Safety Checklist
Before optimizing:
During optimization:
After optimization:
Integration with Other Skills
With Refactoring
- Refactor before optimizing to improve code structure
- Optimization often reveals opportunities for refactoring
- Keep optimizations separate from refactorings in commits
With Testing Strategy
- Write performance tests before optimizing
- Use tests to verify correctness after optimization
- Add regression tests for performance-critical paths
With Monitoring
- Set up monitoring before optimization
- Use metrics to identify optimization targets
- Monitor for performance regressions after deployment
With CI/CD
- Run performance tests in CI pipeline
- Fail builds if performance regresses
- Automate load testing before production deployment
Common Pitfalls
❌ Optimizing without profiling — You'll optimize the wrong thing
❌ Premature optimization — "Premature optimization is the root of all evil" - Donald Knuth
❌ Over-optimization — Sacrificing readability for minimal gains
❌ Ignoring the 80/20 rule — 80% of performance issues come from 20% of code
❌ Not measuring impact — Can't prove optimization worked
❌ Breaking functionality — Tests must pass after optimization
❌ Optimizing the wrong layer — Fix slow algorithm before throwing hardware at it
✅ Profile first, optimize second
✅ Measure baseline and impact
✅ Optimize high-impact bottlenecks
✅ Keep tests passing
✅ Document optimizations
✅ Monitor for regressions
Remember: Performance optimization is a cycle: measure, identify, optimize, measure. Always start with profiling to find actual bottlenecks, and always measure to verify your optimizations work. Fast code that's wrong is useless—keep your tests passing!