The N+1 query is the most common performance bug in applications built on an object-relational mapper, and it is invisible in the source. You fetch a list of orders, loop over them, and read each order's customer. The code reads as one operation. The database sees one query for the orders and then one more per order - four hundred round trips for a page that should have taken two.
It happens because lazy loading is usually the default. The ORM returns objects with relationships unfetched, and touching a relationship triggers a query at that moment. That is a reasonable default for a single object and a disastrous one inside a loop, and the ORM cannot tell the difference. The fix is eager loading - telling the query to fetch the related data up front, with whatever your framework calls it - which turns N+1 into one or two queries.
The reason it survives code review is that it does not appear in development. With twenty rows in a local database, four hundred queries against localhost take a few hundred milliseconds and nobody notices. With forty thousand rows and a database across the network, the same code takes thirty seconds. This is another argument for testing against realistic data volumes, and for the specific practice of logging the query count per request in development, where a page suddenly issuing four hundred queries is obvious.
There are subtler versions worth recognising. Fetching a collection and then counting it in application code, when a COUNT query would do - the ORM loads every row into memory to give you a number. Serialisers that walk relationships during rendering, which produces N+1 in the response layer where nobody is looking for it. And the reverse mistake: eager-loading everything defensively, so a page that needs two fields pulls six related tables and returns megabytes to discard most of it.
The broader point is that an ORM hides the database, and hiding is exactly what makes it productive and what makes it dangerous. It is worth periodically looking at the SQL your application actually emits, in development, for your most-used endpoints. Most frameworks can log it. The gap between what you thought you asked for and what was sent is usually instructive and occasionally alarming.
None of this is an argument against using an ORM - hand-written SQL for every query has its own costs, and the mapping and migration tooling is genuinely valuable. It is an argument for treating the generated SQL as something you are responsible for. The teams that get into trouble are the ones where nobody has looked at a query plan because the ORM was supposed to handle it.