Install
$ agentstack add skill-mickeyyaya-refactoring-skills-performance-anti-patterns ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ● Filesystem access Used
- ● Shell / process execution Used
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Performance Anti-Patterns for Code Review
Overview
Performance anti-patterns are recurring mistakes that degrade throughput, latency, or resource efficiency. They often go undetected in development and surface only under production load. This catalog focuses on signals a reviewer can spot in a PR diff.
Use alongside anti-patterns-catalog (structural problems) and review-code-quality-process (review workflow).
When to Use
- PR touches database queries, HTTP clients, or any I/O layer
- p95 latency or memory usage increased without obvious cause
- Load testing shows non-linear scaling
- PR introduces loops, recursion, or collection transforms
- PR modifies caching logic or connection management
Quick Reference
| Category | Anti-Pattern | Severity | Primary Signal in PR | |----------|-------------|----------|----------------------| | Database | N+1 Query | HIGH | Loop containing a query call | | Database | Unbounded Data Fetching | HIGH | Missing LIMIT/pagination | | Database | Improper Connection Management | HIGH | Connection created inside function | | I/O | Chatty I/O | HIGH | Repeated small I/O calls in a loop | | I/O | Synchronous I/O in Hot Path | HIGH | Blocking call without async/await | | Resilience | Retry Storm | CRITICAL | Retry loop with no backoff or jitter | | Memory | Memory Leak | HIGH | Event listener added without removal | | Concurrency | Blocking the Event Loop | HIGH | CPU-heavy work on main thread | | Efficiency | No Caching | MEDIUM | Identical fetch/compute on every call | | Efficiency | String Concatenation in Loops | MEDIUM | += on strings inside a loop | | Efficiency | Over-rendering | MEDIUM | Missing memoization in render path | | Process | Premature Optimization | LOW | Optimization without profiling comment |
Category 1: Database Anti-Patterns
N+1 Query
Detection: ORM/DB call nested inside a loop or array transform. Lazy-loading ORMs do this by default.
- Python/SQLAlchemy:
session.queryinside a loop over results - TypeScript/Prisma/TypeORM:
findOne/findByIdinsidePromise.all(items.map(...))
// BEFORE — N+1: one query per user
const users = await db.users.findAll();
for (const user of users) {
user.orders = await db.orders.findAll({ where: { userId: user.id } });
}
// AFTER — single JOIN query
const users = await db.users.findAll({
include: [{ model: db.orders }],
});
Fix: JOIN-based eager loading, include/joinedload, or batched query by parent IDs. For REST/GraphQL, use DataLoader-style batching.
Unbounded Data Fetching
Detection: findAll, SELECT *, or getAll without LIMIT, take, or pageSize. Result immediately serialized to JSON signals pagination needed.
// BEFORE — fetches every row
const allOrders = await db.orders.findAll();
// AFTER — paginated query
const { page = 1, pageSize = 50 } = req.query;
const orders = await db.orders.findAll({
limit: Math.min(Number(pageSize), 200),
offset: (Number(page) - 1) * Math.min(Number(pageSize), 200),
order: [['createdAt', 'DESC']],
});
Fix: Mandatory LIMIT+OFFSET or cursor-based pagination. Enforce max page size at API layer. Use streaming for exports.
Improper Connection Management
Detection: new Connection(...), createClient(), or connect() inside a request handler. Connection opened and closed in the same function is not pooling.
// BEFORE — new connection per call
async function getUser(id: string) {
const client = new Client(dbConfig);
await client.connect();
const result = await client.query('SELECT * FROM users WHERE id = $1', [id]);
await client.end();
return result.rows[0];
}
// AFTER — shared pool at module scope
import { Pool } from 'pg';
const pool = new Pool(dbConfig);
async function getUser(id: string) {
const result = await pool.query('SELECT * FROM users WHERE id = $1', [id]);
return result.rows[0];
}
Fix: Initialize pool at startup. Inject via constructor or module singleton. Tune pool size to DB max_connections.
Category 2: I/O Anti-Patterns
Chatty I/O
Detection: fetch, axios.get, redisClient.get, or fs.write inside a loop when bulk APIs exist. Redis GET in a loop should be MGET.
// BEFORE — one request per item
for (const userId of userIds) {
const profile = await fetch(`/api/profiles/${userId}`).then(r => r.json());
profiles.push(profile);
}
// AFTER — single batch request
const profiles = await fetch('/api/profiles/batch', {
method: 'POST',
body: JSON.stringify({ ids: userIds }),
}).then(r => r.json());
Fix: Use bulk/batch APIs. Buffer writes and flush periodically. Redis: MGET/MSET or pipelines.
Synchronous I/O in Hot Path
Detection:
- Node.js:
Syncsuffix (readFileSync,writeFileSync,execSync) - Python async: synchronous
requests,open(), or DB calls withoutawait - Missing
awaitbefore I/O-returning calls
// BEFORE — blocking read on every request
app.get('/config', (req, res) => {
const config = fs.readFileSync('./config.json', 'utf8');
res.json(JSON.parse(config));
});
// AFTER — async read, cached after first load
let cachedConfig: object | null = null;
app.get('/config', async (req, res) => {
if (!cachedConfig) {
const raw = await fs.promises.readFile('./config.json', 'utf8');
cachedConfig = JSON.parse(raw);
}
res.json(cachedConfig);
});
Fix: Replace sync I/O with async equivalents. Python: asyncpg, httpx, aiofiles. Use run_in_executor when async drivers unavailable.
Category 3: Resilience Anti-Patterns
Retry Storm
Detection:
- `while (attempts void;
constructor(private emitter: EventEmitter) { this.handler = this.handleData.bind(this); this.emitter.on('data', this.handler); } handleData(data: unknown) { / ... / } destroy() { this.emitter.off('data', this.handler); } }
```typescript
// BEFORE — unbounded cache
const responseCache = new Map();
// AFTER — bounded LRU cache
import LRU from 'lru-cache';
const responseCache = new LRU({ max: 500, ttl: 60_000 });
Fix: Pair on/addEventListener with off/removeEventListener in cleanup. Use bounded caches (LRU, TTL). Audit closures for unintended references.
Category 5: Concurrency Anti-Patterns
Blocking the Event Loop
Detection:
- CPU-heavy algorithms (sorting, hashing, parsing) in route handlers
JSON.parse(largeBlob)in request cycle- No
worker_threadsor Web Workers for compute-heavy tasks
// BEFORE — CPU work blocks all other requests
app.post('/report', (req, res) => {
const result = generateLargeReport(req.body.data); // blocks for 500ms
res.json(result);
});
// AFTER — offload to worker thread
import { Worker } from 'worker_threads';
app.post('/report', (req, res) => {
const worker = new Worker('./report-worker.js', { workerData: req.body.data });
worker.on('message', (result) => res.json(result));
worker.on('error', (err) => res.status(500).json({ error: err.message }));
});
Fix: Move CPU work to worker_threads or task queue (BullMQ, Celery). Browsers: Web Workers. Moderate cost: chunk with setImmediate.
Category 6: Efficiency Anti-Patterns
No Caching
Detection: Third-party API calls in hot path with no cache check. Expensive aggregation queries without CACHE_TTL. Idempotent pure functions called repeatedly with same args.
Fix: Cache at the boundary where data enters. In-memory (lru-cache) for process-local; Redis/Memcached for shared. Define explicit TTLs. Document invalidation strategy.
String Concatenation in Loops
Detection: += on a string variable inside any loop. String accumulation: let sql = ''; for (...) { sql += ... }.
// BEFORE — O(n^2) allocations
function buildCsv(rows: string[][]): string {
let csv = '';
for (const row of rows) { csv += row.join(',') + '\n'; }
return csv;
}
// AFTER — single join
function buildCsv(rows: string[][]): string {
return rows.map(row => row.join(',')).join('\n');
}
Fix: Collect segments in array, join() once. Python: "".join(...). SQL: use query builder.
Over-rendering
Detection:
useEffect(() => ..., [someObject])wheresomeObjectis{}/[]created each render- Expensive computations in render without
useMemo - Callback props not wrapped in
useCallback
// BEFORE — new object reference triggers re-render every time
function ParentComponent({ items }: { items: Item[] }) {
return ;
}
// AFTER — stable reference with useMemo
function ParentComponent({ items }: { items: Item[] }) {
const filters = useMemo(() => ({ active: true }), []);
const activeItems = useMemo(() => items.filter(i => i.active), [items]);
return ;
}
Fix: useMemo for expensive computations. useCallback for stable callbacks. React.memo on pure children. Lift stable values out of render scope.
Review Checklist by PR Type
| PR touches... | Check for... | |---------------|-------------| | ORM / database queries | N+1 (query in loop), missing LIMIT, new connection per call | | HTTP clients / queues | Single call in loop (Chatty I/O), synchronous client in async handler | | Retry / error handling | Fixed delay, no jitter, no circuit breaker (Retry Storm) | | Event emitters / caches | No cleanup on removal (Memory Leak), unbounded cache growth | | Request handlers (Node.js) | Sync API suffix (readFileSync), CPU work without worker thread | | External API calls | No cache layer for slow-changing data | | String/buffer building | += string in a loop | | React components | Inline object props, expensive compute outside useMemo |
Cross-References
| Related Skill | Relationship | |---------------|-------------| | anti-patterns-catalog | Structural anti-patterns that obscure hot paths | | review-code-quality-process | Workflow for performance-focused reviews | | detect-code-smells | Line-level signals that co-occur with performance anti-patterns | | design-patterns-behavioral | Command for queuing, Observer for decoupled events | | design-patterns-creational-structural | Adapter for wrapping slow vendors; Flyweight for shared instances |
Common Review Mistakes
| Mistake | Correct Approach | |---------|-----------------| | Flagging await in a loop as always wrong | Only N+1 if awaited call is a DB query or unbatched I/O; sequential async is sometimes intentional | | Requiring caching everywhere | Caching adds invalidation complexity; only mandate for proven expensive+stable data | | Treating all string concat as O(n^2) | Modern JS engines optimize small, fixed-iteration cases; flag only unbounded loops | | Blocking event loop vs. slow async path | Slow await does not block the event loop — only synchronous CPU work does | | Demanding memoization for all components | React.memo has overhead; apply only when profiling confirms unnecessary re-renders |
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: mickeyyaya
- Source: mickeyyaya/refactoring-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.