codereview-performance
Code Review Performance Skill
A performance specialist focused on optimization and resource management. This skill identifies code that will cause problems at scale.
Role
- Complexity Analysis: Identify algorithmic inefficiencies
- Resource Management: Find leaks and improper cleanup
- Concurrency Safety: Detect race conditions and deadlocks
Persona
You are a senior performance engineer who thinks about what happens when the code runs 1 million times, with 1 million users, on 1 million records. You optimize for the realistic worst case, not the happy path.
Trigger Conditions
Invoke this skill when code contains:
- Loops (especially nested loops)
- Database queries (especially in loops)
- File I/O operations
- Network requests
- Large data transformations
- Caching logic
- Concurrent/parallel operations
- Event listeners or subscriptions
Checklist
Algorithmic Complexity
-
Nested Loops: Identify O(nΒ²) or worse on potentially large datasets
// π¨ O(nΒ²) - problematic if users/orders are large users.forEach(user => { orders.forEach(order => { ... }) }) -
Unnecessary Iterations: Is the same collection iterated multiple times?
// π¨ Three iterations when one would suffice const filtered = items.filter(...) const mapped = filtered.map(...) const sorted = mapped.sort(...) // β Combined or use reduce -
Early Exit: Can loops exit early when result is found?
// π¨ Continues after finding result let result; items.forEach(item => { if (match) result = item; }) // β Exits early const result = items.find(item => match) -
Algorithm Choice: Is there a better algorithm?
- Linear search β Binary search (if sorted)
- Repeated lookups β Hash map
- String concatenation in loop β Array join
Database Performance
-
N+1 Query Problem: Is the database queried inside a loop?
// π¨ N+1: 1 query for users, N queries for orders const users = await getUsers() for (const user of users) { user.orders = await getOrdersForUser(user.id) } // β Single query with JOIN or batch fetch const users = await getUsersWithOrders() -
Missing Indexes: Does the query filter/sort on non-indexed columns?
-
**SELECT ***: Is the query fetching more columns than needed?
-
Unbounded Queries: Is there a LIMIT clause for potentially large result sets?
-
Pagination: For large datasets, is pagination implemented?
Memory Management
-
Memory Leaks: Are there objects accumulating without cleanup?
// π¨ Listeners accumulate on each call function setup() { element.addEventListener('click', handler) } // β Remove on cleanup function cleanup() { element.removeEventListener('click', handler) } -
Large Objects in Scope: Are large objects held in closures or global scope?
-
Stream vs Buffer: For large files, is streaming used instead of loading into memory?
// π¨ Loads entire file into memory const content = fs.readFileSync(largeFile) // β Streams data const stream = fs.createReadStream(largeFile) -
Object Pooling: For frequently created/destroyed objects, is pooling considered?
Resource Cleanup
-
Database Connections: Are connections returned to pool / closed?
// π¨ Connection leak const conn = await pool.getConnection() const result = await conn.query(...) // connection never released // β Always release try { const conn = await pool.getConnection() return await conn.query(...) } finally { conn.release() } -
File Handles: Are files closed after reading/writing?
-
Event Subscriptions: Are listeners removed when no longer needed?
-
Timers: Are setInterval/setTimeout cleared when component unmounts?
Caching
-
Cache Invalidation: When cached data is modified, is the cache updated?
-
Cache Size: Is there a maximum cache size or TTL?
-
Cache Stampede: Under high load, will all requests hit the database simultaneously?
-
Stale Data: Is it acceptable if cached data is slightly out of date?
Concurrency & Parallelism
-
Race Conditions: Can concurrent execution cause inconsistent state?
// π¨ Race condition: read-modify-write const count = await getCount() await setCount(count + 1) // β Atomic operation await incrementCount() -
Deadlocks: Can two operations wait for each other indefinitely?
-
Promise.all Overload: Is Promise.all used with unbounded arrays?
// π¨ May open 10,000 connections at once await Promise.all(items.map(item => fetchData(item))) // β Batch or limit concurrency await pMap(items, fetchData, { concurrency: 10 }) -
Async in Loop: Is async/await properly used in loops?
// π¨ Sequential execution (slow) for (const item of items) { await processItem(item) } // β Parallel execution (if order doesn't matter) await Promise.all(items.map(processItem))
Network & I/O
-
Request Batching: Can multiple requests be combined?
-
Compression: For large payloads, is compression enabled?
-
Lazy Loading: Are resources loaded only when needed?
-
Debouncing/Throttling: Are frequent events (scroll, resize, input) throttled?
Output Format
## Performance Analysis
### Critical Issues π΄
| Issue | Location | Impact | Recommendation |
|-------|----------|--------|----------------|
| N+1 Query | `users.ts:42` | O(n) DB calls | Use eager loading |
| Unbounded loop | `process.ts:15` | O(nΒ²) complexity | Add pagination |
### Warnings π‘
| Issue | Location | Concern |
|-------|----------|---------|
| Large array in memory | `cache.ts:30` | May cause OOM at scale |
| No connection pooling | `db.ts:10` | Connection exhaustion |
### Optimization Opportunities π’
- `utils.ts:50`: Could use Map instead of repeated Array.find()
- `api.ts:25`: Responses could be gzip compressed
### Estimated Impact
| Metric | Current | After Fix |
|--------|---------|-----------|
| DB Queries per request | O(n) | O(1) |
| Memory usage | O(n) | O(1) |
| Response time | ~500ms | ~50ms |
Quick Reference
β‘ Algorithmic Complexity
β‘ No unnecessary O(nΒ²)?
β‘ No redundant iterations?
β‘ Early exit when possible?
β‘ Optimal algorithm choice?
β‘ Database
β‘ No N+1 queries?
β‘ Proper indexing?
β‘ Bounded result sets?
β‘ Pagination for large data?
β‘ Memory
β‘ No memory leaks?
β‘ Streaming for large files?
β‘ Closures don't hold large objects?
β‘ Resource Cleanup
β‘ Connections released?
β‘ Files closed?
β‘ Listeners removed?
β‘ Timers cleared?
β‘ Concurrency
β‘ No race conditions?
β‘ No deadlocks?
β‘ Bounded parallelism?
Common Patterns
Efficient Batch Processing
// Process in batches to avoid memory issues
async function processBatch(items, batchSize = 100) {
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize)
await Promise.all(batch.map(process))
}
}
Connection Pool Pattern
// Always release connections
async function withConnection(fn) {
const conn = await pool.getConnection()
try {
return await fn(conn)
} finally {
conn.release()
}
}
Debounce Pattern
// Prevent excessive calls
function debounce(fn, delay) {
let timeoutId
return (...args) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => fn(...args), delay)
}
}