Querying collections in Laravel
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
Querying Collections in Laravel: Database Queries vs. In-Memory Processing
As senior developers working with Eloquent and Laravel, understanding the distinction between accessing a relationship as a property versus calling it as a method is fundamental to writing efficient and predictable code. The question you raise—whether querying a collection queries the database or works in memory—touches upon the core mechanics of lazy loading in Eloquent.
Let's break down exactly what happens under the hood when you interact with Laravel Collections derived from Eloquent relationships.
Understanding the Distinction: Property vs. Method
The difference lies entirely in when the underlying database query is executed. This behavior is governed by how Eloquent handles accessing relationship properties versus calling methods that trigger database interactions.
Consider your example involving a belongsToMany relationship:
public function permissions()
{
return $this->belongsToMany(Permission::class, RolePermission::getModelTable(), 'role_id', 'permission_id');
}
Scenario 1: Accessing the Collection (In-Memory)
When you access the relationship as a property, Laravel retrieves the already loaded data from memory. This is what happens when you want to iterate over the results that were previously fetched or are currently available in the object.
$role = Role::find(1); // Assume $role is fetched from the database
// Accessing the relationship as a property (Collection)
$permissions = $role->permissions;
// Performing an operation on the collection (in-memory processing)
$count = $permissions->where('code', 'global.test')->count();
In this case, $role->permissions simply returns the Eloquent Collection object that was loaded when the $role model was initialized, or it triggers a lazy load if it hasn't been loaded yet (which is the default behavior). When you call methods like where() or count() on this collection object, these operations are performed entirely within PHP memory. No new SQL query is executed against the database for this specific operation.
Scenario 2: Calling the Relationship as a Method (Database Query)
When you access the relationship by calling it as a method, you are explicitly instructing Eloquent to execute a fresh query against the database to fetch the related records.
// Calling the relationship as a method
$permissionsQuery = $role->permissions()->where('code', 'global.test')->count();
Here, the -> syntax signals to Eloquent that a new query needs to be constructed and executed against the database to retrieve the related permissions before the counting operation can occur. This is the mechanism behind lazy loading—it defers the execution of the SQL until it is explicitly requested.
Efficiency and Best Practices: When to Query?
The efficiency of your application heavily depends on when you choose to query the database. The primary performance pitfall in relational databases, especially when dealing with collections, is the N+1 problem. This occurs when you loop through a set of parent models (e.g., roles) and then execute a separate query for each related item (e.g., permissions).
Avoiding N+1: Eager Loading is Key
The most efficient way to handle relationships is by using Eager Loading. Instead of letting Eloquent perform individual queries inside a loop, you instruct it to fetch all necessary related data in one or two optimized SQL queries upfront. This ensures that the relationship query is executed only once, significantly reducing database load and latency.
To eagerly load permissions for multiple roles:
// Eager loading: Fetches all necessary data in minimal queries (usually 2)
$roles = Role::with('permissions')->get();
// Now, accessing the collection is fast because the data is already loaded
foreach ($roles as $role) {
// This is now an in-memory operation, no new DB query per role
$count = $role->permissions->where('code', 'global.test')->count();
// ... process data
}
As demonstrated by the principles of efficient data retrieval championed by Laravel, eager loading prevents the N+1 problem, making your code significantly faster and more scalable. When dealing with complex relationships, always favor methods like with() over relying on implicit lazy loading when performance is a concern.
Conclusion
To summarize: treat $model->relation as an in-memory collection for operations, and treat $model->relation() as a command to execute a database query. For optimal performance, especially when dealing with collections of models, always prioritize eager loading over lazy loading to ensure your application remains fast and efficient.