Bird
Raised Fist0
Laravelframework~10 mins

Lazy loading and N+1 prevention in Laravel - Step-by-Step Execution

Choose your learning style10 modes available
Concept Flow - Lazy loading and N+1 prevention
Start Query for Posts
↓
Lazy Load Posts
↓
Access Related Comments?
↓
No Extra Query
↓
N+1 Problem if in Loop
↓
Use Eager Loading to Prevent N+1
↓
Single Query for Comments
↓
Display Data
↓
End
This flow shows how lazy loading fetches related data only when accessed, which can cause many queries (N+1 problem), and how eager loading prevents this by fetching all needed data upfront.
Execution Sample
Laravel
use App\Models\Post;

$posts = Post::all();

foreach ($posts as $post) {
    echo $post->comments->count();
}
This code fetches all posts, then for each post accesses its comments, triggering lazy loading and causing N+1 queries.
Execution Table
StepActionQuery ExecutedResultNotes
1Fetch all postsSELECT * FROM postsCollection of postsOne query to get all posts
2Start loop: first postNo query yetAccess first post objectNo comments queried yet
3Access comments of first postSELECT * FROM comments WHERE post_id = 1Comments collection for post 1Lazy loading triggers query
4Output comments count for first postNo queryNumber of comments for post 1Count displayed
5Loop: second postNo query yetAccess second post objectNo comments queried yet for post 2
6Access comments of second postSELECT * FROM comments WHERE post_id = 2Comments collection for post 2Lazy loading triggers query again
7Output comments count for second postNo queryNumber of comments for post 2Count displayed
8Repeat for all postsOne query per post for commentsComments collectionsN+1 queries total
9End loopNo queryAll posts processedN+1 problem evident
💡 Loop ends after all posts processed; N+1 queries executed due to lazy loading inside loop.
Variable Tracker
VariableStartAfter Step 1After Step 3After Step 6Final
$postsnullCollection of all postsSame collectionSame collectionSame collection
$postnullFirst post objectFirst post objectSecond post objectLast post object
$post->commentsnullnullComments collection for post 1Comments collection for post 2Comments collection for last post
Key Moments - 3 Insights
Why does accessing $post->comments inside the loop cause many queries?
Because lazy loading fetches related comments only when accessed, so each post triggers a separate query as shown in steps 3 and 6 of the execution_table.
How can we prevent the N+1 problem in this example?
By using eager loading (e.g., Post::with('comments')->get()), which fetches all posts and their comments in just two queries, avoiding the repeated queries inside the loop.
Does lazy loading always cause N+1 queries?
No, lazy loading only causes N+1 queries when related data is accessed repeatedly in a loop, as shown in the execution_table steps.
Visual Quiz - 3 Questions
Test your understanding
Look at the execution_table, what query runs at step 6?
ASELECT * FROM comments WHERE post_id = 2
BSELECT * FROM posts
CSELECT * FROM comments WHERE post_id = 1
DNo query runs
💡 Hint
Check the 'Query Executed' column at step 6 in the execution_table.
At which step does the N+1 problem become clear?
AStep 1
BStep 8
CStep 3
DStep 9
💡 Hint
Look for the step mentioning 'One query per post for comments' in the Notes column.
If we use eager loading, how would the number of queries change?
AIt would stay the same
BIt would increase
CIt would reduce to two queries
DIt would reduce to zero queries
💡 Hint
Refer to the key_moments section explaining eager loading benefits.
Concept Snapshot
Lazy loading fetches related data only when accessed, causing many queries if done inside loops (N+1 problem).
Eager loading fetches all related data upfront with fewer queries.
Use Post::with('comments')->get() to prevent N+1.
Avoid accessing relations inside loops without eager loading.
This improves performance by reducing database queries.
Full Transcript
This visual execution trace shows how Laravel's lazy loading works by fetching related comments only when accessed on each post. The example code fetches all posts, then loops through them, accessing comments for each post. This triggers a separate query per post for comments, causing the N+1 problem. The execution table details each step, showing queries run and data accessed. The variable tracker shows how variables change during execution. Key moments clarify why lazy loading inside loops causes many queries and how eager loading prevents this by fetching all related data in fewer queries. The quiz tests understanding of query steps and N+1 prevention. The snapshot summarizes the concept for quick reference.

Practice

(1/5)
1. What is the main purpose of using with() in Laravel Eloquent queries?
easy
A. To delete related models automatically
B. To load related models eagerly and prevent the N+1 query problem
C. To delay loading related models until they are accessed
D. To paginate the main query results

Solution

  1. Step 1: Understand lazy vs eager loading

    Lazy loading delays loading related data until accessed, causing multiple queries (N+1 problem).
  2. Step 2: Role of with()

    The with() method loads related models eagerly in one query, preventing multiple queries.
  3. Final Answer:

    To load related models eagerly and prevent the N+1 query problem -> Option B
  4. Quick Check:

    Eager loading = with() usage [OK]
Hint: Use with() to load relations early and avoid many queries [OK]
Common Mistakes:
  • Confusing lazy loading with eager loading
  • Thinking with() delays loading
  • Assuming with() deletes data
  • Mixing with() with pagination
2. Which of the following is the correct syntax to eager load the 'comments' relation on a Post model?
easy
A. $posts = Post::with('comments')->get();
B. $posts = Post::load('comments')->get();
C. $posts = Post::eager('comments')->get();
D. $posts = Post::fetch('comments')->get();

Solution

  1. Step 1: Identify Laravel eager loading method

    Laravel uses with() to eager load relations, not load() or others in this context.
  2. Step 2: Check method chaining and syntax

    with('comments') is called before get() to eager load comments with posts.
  3. Final Answer:

    $posts = Post::with('comments')->get(); -> Option A
  4. Quick Check:

    Eager load syntax = with() before get() [OK]
Hint: Remember: with() before get() for eager loading [OK]
Common Mistakes:
  • Using load() statically instead of on model instance
  • Using non-existent methods like eager() or fetch()
  • Calling with() after get()
3. Given the code below, what will be the total number of database queries executed?
 $users = User::all();
foreach ($users as $user) {
echo $user->posts->count();
}
medium
A. N+1 queries, where N is the number of users
B. 1 query
C. N queries, where N is the number of posts
D. 2 queries regardless of users count

Solution

  1. Step 1: Analyze the initial query

    User::all() runs 1 query to get all users.
  2. Step 2: Check relation access inside loop

    Accessing $user->posts triggers a query per user (lazy loading), causing N additional queries.
  3. Final Answer:

    N+1 queries, where N is the number of users -> Option A
  4. Quick Check:

    Lazy loading inside loop = N+1 queries [OK]
Hint: Accessing relations in loop without with() causes N+1 queries [OK]
Common Mistakes:
  • Assuming only 1 query runs
  • Confusing number of queries with posts count
  • Thinking 2 queries run regardless of data size
4. Identify the error in the following code that tries to prevent N+1 queries:
 $orders = Order::with('items');
foreach ($orders as $order) {
echo $order->items->count();
}
medium
A. Incorrect relation name 'items', should be 'item'
B. Loop variable should be $orders not $order
C. Using with() causes lazy loading, should use load()
D. Missing get() after with() to execute the query

Solution

  1. Step 1: Check query execution

    Order::with('items') builds a query but does not execute it without get() or similar.
  2. Step 2: Effect on foreach loop

    Without get(), $orders is a query builder, not a collection, so foreach will error.
  3. Final Answer:

    Missing get() after with() to execute the query -> Option D
  4. Quick Check:

    Always call get() to run query after with() [OK]
Hint: Add get() after with() to fetch data [OK]
Common Mistakes:
  • Forgetting to execute query with get()
  • Assuming with() runs query immediately
  • Confusing relation names without checking model
  • Misunderstanding foreach variable naming
5. You want to display a list of authors with their books and each book's reviews without causing N+1 queries. Which is the best way to write the query?
hard
A. $authors = Author::with('reviews')->get();
B. $authors = Author::with('books')->get(); foreach ($authors as $author) { $author->books->load('reviews'); }
C. $authors = Author::with('books.reviews')->get();
D. $authors = Author::all(); foreach ($authors as $author) { foreach ($author->books as $book) { echo $book->reviews->count(); } }

Solution

  1. Step 1: Understand nested eager loading

    To avoid N+1, load related models and their relations in one query using dot notation.
  2. Step 2: Analyze options for efficiency

    $authors = Author::with('books.reviews')->get(); uses with('books.reviews') to eager load books and their reviews together, preventing multiple queries.
  3. Final Answer:

    $authors = Author::with('books.reviews')->get(); -> Option C
  4. Quick Check:

    Nested eager loading = dot notation in with() [OK]
Hint: Use dot notation in with() for nested relations [OK]
Common Mistakes:
  • Loading relations inside loops causing N+1
  • Not eager loading nested relations together
  • Using wrong relation names
  • Assuming with('reviews') loads nested reviews
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard