Every Laravel project I have inherited has the same line sitting in a Blade template, written by someone in a hurry and never looked at again: $order->customer->name, echoed in a loop. It reads beautifully. It is also, inside a loop, a separate database query per row.
Eloquent’s great trick is that relationships feel like properties. That is also its trap: nothing in the syntax tells you which attribute is already in memory and which one is about to hit the database.
Lazy loading is a syntax problem
The reason N+1 survives code review is that the broken version and the correct version look identical at the call site. The difference lives in the controller, several files away.
This is heading 3
The database is not a resource you scale. It is a resource you protect.
Make the framework tell you
One line in AppServiceProvider turns every lazy load into an exception locally:
public function boot(): void
{
Model::preventLazyLoading(! app()->isProduction());
}
Three habits that keep it fixed
Eager load in the query, never in the view. The controller decides what gets loaded.
Select the columns you need. Pulling forty columns to render one is waste.
Assert query counts in tests. A failing test beats profiling later.
What happened when we shipped it
One client’s orders index went from 340 queries to 6. No caching, no new indexes, just three with() calls and one guard in a service provider.
Let's try some python code
def my_function():
print("Hello from a function")