Review Performance
Performance analysis for common bottlenecks and inefficiencies.
Usage
/review-perf # Review context-related code /review-perf --staged # Review staged changes /review-perf --all # Full codebase audit (parallel agents)
Scope
Flag Scope Method
(none) Context-related code Files from the current conversation context: any files the user has discussed, opened, or that you have read/edited in this session. If no conversation context exists, ask the user to specify files or use --staged /--all .
--staged
Staged changes git diff --cached --name-only
--all
Full codebase Glob source files, parallel agents
Workflow
-
Determine scope based on flags (see Scope table above)
-
Review each file against all 5 categories in the Performance Checklist below: Algorithmic Complexity, Database/Query Patterns, Memory Management, UI/Render Performance, Network/IO
-
Parallelize if scope has >5 files: spawn one sub-agent per category, each scanning all files for that category. Merge results and deduplicate.
-
Classify severity for each finding:
-
Critical: User-facing slowdown, data loss risk, or resource exhaustion (e.g., memory leak, N+1 on hot path)
-
High: Measurable inefficiency on a common code path but not immediately user-visible (e.g., O(n²) on lists typically < 100 items but growing)
-
Medium: Suboptimal pattern that could become a problem at scale (e.g., missing pagination, sequential requests that could be parallel)
-
Suggestion: Optimization opportunity with marginal current impact
-
Report findings grouped by severity using the Output Format below
Performance Checklist
Algorithmic Complexity
O(n²) or Worse:
// BAD: O(n²) nested loops for (const item of items) { for (const other of items) { if (item.id === other.parentId) { ... } } }
// GOOD: O(n) with lookup map const parentMap = new Map(items.map(i => [i.id, i])); for (const item of items) { const parent = parentMap.get(item.parentId); }
Repeated Calculations:
// BAD: Recalculating in loop items.forEach(item => { const config = expensiveConfigLookup(); // Called n times process(item, config); });
// GOOD: Calculate once const config = expensiveConfigLookup(); items.forEach(item => process(item, config));
Missing Early Exit:
// BAD: Always iterates entire array function findUser(users, id) { let result = null; users.forEach(u => { if (u.id === id) result = u; }); return result; }
// GOOD: Exit when found function findUser(users, id) { return users.find(u => u.id === id); }
Database/Query Patterns
N+1 Queries:
// BAD: Query per item const users = await db.users.findAll(); for (const user of users) { user.posts = await db.posts.findAll({ where: { userId: user.id } }); }
// GOOD: Single query with include const users = await db.users.findAll({ include: [{ model: db.posts }] });
Missing Pagination:
// BAD: Load all records const allUsers = await db.users.findAll();
// GOOD: Paginate const users = await db.users.findAll({ limit: 50, offset: page * 50 });
SELECT * When Few Columns Needed:
-- BAD: Fetching everything SELECT * FROM users WHERE active = true;
-- GOOD: Only needed columns SELECT id, name, email FROM users WHERE active = true;
Missing Indexes:
-
Columns used in WHERE clauses
-
Columns used in ORDER BY
-
Foreign key columns
-
Columns used in JOIN conditions
Memory Management
Unclosed Resources:
// BAD: Connection never closed const conn = await db.connect(); const data = await conn.query('...'); // conn stays open
// GOOD: Always close const conn = await db.connect(); try { return await conn.query('...'); } finally { conn.close(); }
Growing Caches:
// BAD: Cache grows forever const cache = {}; function getValue(key) { if (!cache[key]) cache[key] = expensiveLookup(key); return cache[key]; }
// GOOD: LRU or TTL cache const cache = new LRUCache({ max: 1000 });
Event Listener Leaks:
// BAD: Never removed useEffect(() => { window.addEventListener('resize', handler); }, []);
// GOOD: Cleanup useEffect(() => { window.addEventListener('resize', handler); return () => window.removeEventListener('resize', handler); }, []);
UI/Render Performance
Unnecessary Re-renders (React):
// BAD: New object every render <Child style={{ color: 'red' }} /> <Child onClick={() => handleClick(id)} />
// GOOD: Memoize const style = useMemo(() => ({ color: 'red' }), []); const handleClickMemo = useCallback(() => handleClick(id), [id]);
Missing Virtualization:
// BAD: Render 10,000 items {items.map(item => <Row key={item.id} {...item} />)}
// GOOD: Use virtualization <VirtualizedList data={items} renderItem={({ item }) => <Row {...item} />} />
Blocking Main Thread:
// BAD: Heavy sync computation function processData(data) { return data.map(item => expensiveTransform(item)); // Blocks UI }
// GOOD: Use web worker or chunk async function processData(data) { return await worker.process(data); }
Network/IO
Sequential Requests:
// BAD: Wait for each const user = await fetchUser(id); const posts = await fetchPosts(id); const comments = await fetchComments(id);
// GOOD: Parallel const [user, posts, comments] = await Promise.all([ fetchUser(id), fetchPosts(id), fetchComments(id) ]);
Missing Request Deduplication:
// BAD: Same request multiple times componentA.fetchUser(123); componentB.fetchUser(123); // Duplicate request
// GOOD: Cache or dedupe
const { data } = useSWR(/users/${id}, fetcher);
Output Format
Performance Review: {scope}
Critical (user-facing slowdown)
- {file}:{line} - {issue type}: {description} Impact: {why it matters} Fix: {solution with code example}
High Priority
- {file}:{line} - {issue} Fix: {solution}
Medium Priority
- {file} - {issue}
Suggestions
- {optimization opportunity}
Examples
Staged changes introduce N+1 query:
/review-perf --staged
Reviews staged files and catches a new user list endpoint that queries posts per user in a loop. Reports it as Critical with the impact ("100 users = 101 queries") and provides a fix using eager loading with include .
Full audit finds memory leak in dashboard:
/review-perf --all
Parallel agents scan the full codebase by category. Finds an event listener in the dashboard component that is never cleaned up on unmount, plus an unbounded in-memory cache growing with every API call.
Troubleshooting
False positive on a rarely-executed code path
Solution: If the flagged code runs only during initialization or in admin-only flows, note the expected data size in a code comment. Re-run the review and the context will help distinguish hot paths from cold ones.
Cannot determine algorithmic complexity without runtime data
Solution: Add a brief comment with the expected input size (e.g., // n is typically < 50 ) so static analysis can assess impact. For uncertain cases, use /perf-test to measure actual performance with realistic data.
Notes
-
Focus on measurable impact, not micro-optimizations
-
Consider data size - O(n²) on 10 items is fine, on 10,000 is not
-
For --all , use parallel agents per category
-
Database issues often have the highest impact
-
UI issues matter most for user-facing code