Database or API call inside a loop
A database, HTTP or model call made once per loop iteration.
Why it matters
A model writes the correct call for one item and then places it inside a loop over many. The result reads naturally, type-checks, and passes a test with three fixtures, so nothing points at it.
In production the list is longer. One request becomes a thousand sequential database round-trips, the endpoint times out, and the connection pool is drained for every other request on the machine. With model calls, each iteration is billed and rate-limited on its own, so cost and latency grow with the data.
Example
export async function attachAuthors(posts: Post[]) {
const result = [];
for (const post of posts) {
- result.push({ ...post, author: authorsById.get(post.authorId) });
+ const author = await prisma.user.findUnique({ where: { id: post.authorId } });
+ result.push({ ...post, author });
}
return result;
}
What it detects
- Added lines inside a loop body (for, while, do, forEach, map, filter, reduce, and Python for and while) that run a database query or ORM call (Prisma, Django, SQLAlchemy, Supabase, knex, SQL templates), an LLM or embedding call, an HTTP request, or synchronous file I/O.
- Containment is decided from a structural model of the file after the change, with strings and comments blanked, brace depth for JavaScript and Go and indentation for Python, rather than from line proximity.
- Not flagged: loops already batched with Promise.all, asyncio.gather, pMap or executemany; pagination and retry loops; loops bounded at three or fewer; loops over an array literal of up to eight items; JSX render loops; and DDL statements. Synchronous file reads count only inside an async function outside script and CLI directories.
- Database and model calls are rated high, HTTP and file calls medium, and anything in a test file low.
What to do when it fires
Move the call out of the loop and fetch the whole set once, with WHERE id IN (...), a join, or the ORM's findMany form, then look results up from a map inside the loop.
For HTTP and model calls, use the provider's batch endpoint if there is one. Otherwise collect the calls and run them together with Promise.all or asyncio.gather under a small concurrency limit.