When working with Eloquent ORM, there's a danger of using collection operations (like filter() and map()) on Eloquent results instead of applying conditions at the DB query level.
The Problem
Consider this common approach:
$listings = Listing::with('tags')->latest()->get(); if ($request->has('s')) { $searchTerm = $request->input('s'); $listings = $listings->filter(function($listing) use ($searchTerm) { return str_contains($listing->title, $searchTerm) || str_contains($listing->company, $searchTerm) || str_contains($listing->location, $searchTerm); });} if ($request->has('tag')) { $tag = $request->input('tag'); $listings = $listings->filter(function($listing) use ($tag) { return $listing->tags->contains('slug', $tag); });} // Then take only what we need$listings = $listings->take(10);
This might seem clean and work well with small amounts of data. However, it introduces two critical issues:
- Memory Usage: The application loads ALL records from the database into memory before filtering them.
- Processing Speed: PHP has to process the filtering logic, which is much slower than database engines.
Real-World Example
I have created a simple job board application page and seeded 4000 listings. On the front end, I show only the ten latest listings, as seen in the code above. The search and tag filtering is done via the filter() method from a Collection.
Using collection operations (filter() after get()) we have a few problems:
- Memory usage: 28MB
- Models loaded: 10008 (all listings plus relations)
- Page load: 114ms

Despite only displaying 10 listings on the homepage, we're loading EVERY listing into memory! While the page loads relatively fast with many users, your application can run out of memory quickly.
The Solution: Move Filtering to the Query Builder
The correct approach is to build the query progressively and only execute it when a condition is...