When I first started using ORMs in PHP, I loved how clean and readable my code became. But I quickly ran into performance problemsāthings were getting sluggish, and I couldnāt figure out why. Thatās when I discovered the magic (and danger) of lazy loading, and why eager loading is a tool every developer needs in their back pocket.
What is Eager Loading?
Eager loading is the practice of loading related data at the same time as the main data query, typically using a JOIN
. Instead of fetching each related record one at a time (which leads to the dreaded N+1 problem), we ask for everything up front in a single efficient query.
Sample Database Schema
Users Table:
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(255)
);
Posts Table:
CREATE TABLE posts (
id INT PRIMARY KEY,
user_id INT,
title VARCHAR(255),
FOREIGN KEY (user_id) REFERENCES users(id)
);
A Common Example (Without Eager Loading)
Letās say youāre pulling users and each one has a post.
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
// Step 1: Get all users
$stmt = $pdo->query("SELECT * FROM users");
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Step 2: For each user, fetch their posts (one query per user!)
foreach ($users as $user) {
echo "User: {$user['name']}\n";
$stmt = $pdo->prepare("SELECT * FROM posts WHERE user_id = ?");
$stmt->execute([$user['id']]);
$posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($posts as $post) {
echo "- {$post['title']}\n";
}
}
This looks fineābut under the hood, this results in 1 query to fetch all users and 1 query per user to fetch the posts. So if you have 100 users, thatās 101 queries. Yikes.
With Eager Loading
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
// Step 1: Get all users
$stmt = $pdo->query("SELECT * FROM users");
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Step 2: Get all posts in one query
$stmt = $pdo->query("SELECT * FROM posts");
$posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Step 3: Group posts by user_id
$postsByUser = [];
foreach ($posts as $post) {
$postsByUser[$post['user_id']][] = $post;
}
// Step 4: Display users and their posts
foreach ($users as $user) {
echo "User: {$user['name']}\n";
$userPosts = $postsByUser[$user['id']] ?? [];
foreach ($userPosts as $post) {
echo "- {$post['title']}\n";
}
}
Now weāre down to 2 queries totalāone for users, one for posts. Much better.
But, I know it still looks clunky having to group them in PHP so here’s another example – this time just using one query.
Eager Loading via JOIN
$stmt = $pdo->query("
SELECT users.id AS user_id, users.name AS user_name,
posts.id AS post_id, posts.title AS post_title
FROM users
LEFT JOIN posts ON posts.user_id = users.id
ORDER BY users.id
");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Group and display
$currentUserId = null;
foreach ($rows as $row) {
if ($row['user_id'] !== $currentUserId) {
$currentUserId = $row['user_id'];
echo "User: {$row['user_name']}\n";
}
if ($row['post_id']) {
echo "- {$row['post_title']}\n";
}
}
Cool huh?
Common Use-Cases for Eager Loading
- Displaying lists with related data (e.g., users and roles, orders and products)
- APIs that return nested JSON (e.g., user ā posts ā comments)
- Dashboards that show summarized data across tables
- Admin panels where you paginate through relational data
- Search results with filters based on related models
Basically, if you’re planning to show related data alongside your main data every time, go eager.
When Not to Use Eager Loading
Eager loading isnāt always a free win. Avoid it when:
- You donāt need the related data right away
- Youāre dealing with huge relationships and donāt want to balloon your query memory
- You conditionally access the relationship (e.g., only sometimes showing the related data)
- Youāre fetching something with deeply nested relations that are rarely needed
Premature eager loading can lead to bigger, slower queries than necessaryāespecially when relationships are large or optional.
+ Pro Tip
- Paginate your queries. Only load what you need. You don’t want to load all 500 users with 500 posts each right if you only need the 10 most recent posts of the 10 most active users. Capische?
Wrap Up
Understanding when and how to use eager loading can be the difference between a fast app and one that grinds to a halt under load. The trick is balance: eager load only what you know youāll need, and keep an eye on your query logs to avoid surprises.
I recommend profiling your pages and learning what your ORM is actually doing under the hood. Once youāve seen the N+1 monster in action, youāll never forget to go eager again.