Navigation

Laravel

Difference between `withCount()` and `with()->count()`

Optimize Laravel relationship counting in 2025 using withCount() vs with()->count(). Avoid N+1 queries when displaying counts vs. accessing related data.

Table Of Contents

The Performance Problem

When displaying relationship counts, there's a crucial difference between getting just the count versus loading all related records then counting them:

// INEFFICIENT - Loads ALL posts, then counts in PHP
$users = User::with('posts')->get();
foreach ($users as $user) {
    echo $user->posts->count(); // N+1 queries + memory waste
}

// EFFICIENT - Gets count from database without loading data
$users = User::withCount('posts')->get();
foreach ($users as $user) {
    echo $user->posts_count; // Single query, minimal memory
}

// WRONG - This doesn't work (syntax error)
$users = User::with()->count('posts')->get(); // Invalid syntax

// The comparison is: withCount() vs with()->count()
// with()->count() loads all records then counts
// withCount() gets the count from database

Laravel withCount() vs with()->count() Performance

Understanding when to use each approach for optimal performance:

// Scenario 1: Only need counts - Use withCount()
$categories = Category::withCount(['posts', 'products'])->get();
foreach ($categories as $category) {
    echo "{$category->name}: {$category->posts_count} posts, {$category->products_count} products";
}
// Generated SQL: SELECT categories.*, (SELECT COUNT(*) FROM posts...) as posts_count

// Scenario 2: Need both data and count - Use with() then count()
$users = User::with('posts')->get();
foreach ($users as $user) {
    echo "Posts by {$user->name}:";
    foreach ($user->posts as $post) {
        echo "- {$post->title}"; // Using the actual data
    }
    echo "Total: {$user->posts->count()}"; // Count from loaded collection
}

// Scenario 3: Need filtered counts - withCount() with conditions
$users = User::withCount(['posts' => function ($query) {
    $query->where('published', true);
}])->get();

foreach ($users as $user) {
    echo "{$user->name} has {$user->posts_count} published posts";
}

// Scenario 4: Multiple count variations
$users = User::withCount([
    'posts', // All posts
    'posts as published_posts_count' => function ($query) {
        $query->where('published', true);
    },
    'posts as draft_posts_count' => function ($query) {
        $query->where('published', false);
    }
])->get();

Advanced Count Scenarios and Optimization

Handle complex counting requirements efficiently:

// E-commerce: Product statistics
$categories = Category::withCount([
    'products',
    'products as active_products_count' => function ($query) {
        $query->where('active', true);
    },
    'products as out_of_stock_count' => function ($query) {
        $query->where('stock', 0);
    },
    'products as featured_count' => function ($query) {
        $query->where('featured', true);
    }
])->get();

// Blog: User engagement metrics
$users = User::withCount([
    'posts',
    'comments',
    'posts as recent_posts_count' => function ($query) {
        $query->where('created_at', '>=', now()->subDays(30));
    },
    'comments as recent_comments_count' => function ($query) {
        $query->where('created_at', '>=', now()->subDays(30));
    }
])->get();

foreach ($users as $user) {
    $totalActivity = $user->posts_count + $user->comments_count;
    $recentActivity = $user->recent_posts_count + $user->recent_comments_count;
    
    echo "{$user->name}: {$totalActivity} total, {$recentActivity} recent";
}

// Complex nested relationship counting
$posts = Post::withCount([
    'comments',
    'comments as approved_comments_count' => function ($query) {
        $query->where('approved', true);
    },
    'comments as recent_comments_count' => function ($query) {
        $query->where('created_at', '>=', now()->subWeek());
    },
    'likes',
    'bookmarks'
])->get();

// Performance comparison demo
class PerformanceTest
{
    public function inefficientApproach()
    {
        // DON'T DO THIS - Loads all data into memory
        $startTime = microtime(true);
        $startMemory = memory_get_usage();
        
        $users = User::with(['posts', 'comments'])->get();
        
        $stats = [];
        foreach ($users as $user) {
            $stats[] = [
                'user_id' => $user->id,
                'posts_count' => $user->posts->count(), // PHP count
                'comments_count' => $user->comments->count() // PHP count
            ];
        }
        
        $endTime = microtime(true);
        $endMemory = memory_get_usage();
        
        return [
            'time' => $endTime - $startTime,
            'memory' => $endMemory - $startMemory,
            'stats' => $stats
        ];
    }
    
    public function efficientApproach()
    {
        // DO THIS - Gets counts from database
        $startTime = microtime(true);
        $startMemory = memory_get_usage();
        
        $users = User::withCount(['posts', 'comments'])->get();
        
        $stats = [];
        foreach ($users as $user) {
            $stats[] = [
                'user_id' => $user->id,
                'posts_count' => $user->posts_count, // Database count
                'comments_count' => $user->comments_count // Database count
            ];
        }
        
        $endTime = microtime(true);
        $endMemory = memory_get_usage();
        
        return [
            'time' => $endTime - $startTime,
            'memory' => $endMemory - $startMemory,
            'stats' => $stats
        ];
    }
}

// Real-world dashboard example
class DashboardController extends Controller
{
    public function index()
    {
        // Efficient approach for dashboard stats
        $stats = [
            'users' => User::withCount([
                'posts',
                'comments',
                'orders' => function ($query) {
                    $query->where('status', 'completed');
                }
            ])->paginate(20),
            
            'categories' => Category::withCount([
                'posts' => function ($query) {
                    $query->where('published', true);
                }
            ])->get(),
            
            'products' => Product::withCount([
                'reviews',
                'orders' => function ($query) {
                    $query->where('created_at', '>=', now()->subMonth());
                }
            ])->where('featured', true)->get()
        ];
        
        return view('dashboard', compact('stats'));
    }
}

// API endpoint for mobile app
class UserController extends Controller
{
    public function profile($id)
    {
        $user = User::withCount([
            'posts',
            'followers',
            'following',
            'posts as published_posts_count' => function ($query) {
                $query->where('published', true);
            }
        ])->findOrFail($id);
        
        return response()->json([
            'id' => $user->id,
            'name' => $user->name,
            'stats' => [
                'total_posts' => $user->posts_count,
                'published_posts' => $user->published_posts_count,
                'followers' => $user->followers_count,
                'following' => $user->following_count
            ]
        ]);
    }
}

// Blade template usage
<!-- Efficient count display -->
@foreach($users as $user)
    <div class="user-card">
        <h3>{{ $user->name }}</h3>
        <div class="stats">
            <span>{{ $user->posts_count }} posts</span>
            <span>{{ $user->comments_count }} comments</span>
            <span>{{ $user->published_posts_count }} published</span>
        </div>
    </div>
@endforeach

<!-- When you need both count and data -->
@foreach($categories as $category)
    <div class="category">
        <h3>{{ $category->name }} ({{ $category->posts_count }} posts)</h3>
        
        @if($category->relationLoaded('posts'))
            <!-- Show actual posts if loaded with with() -->
            @foreach($category->posts->take(3) as $post)
                <p>{{ $post->title }}</p>
            @endforeach
        @endif
    </div>
@endforeach

Use withCount() when you only need relationship counts - it's much more efficient. Use with() followed by ->count() only when you need both the actual relationship data AND the count. Never use with() just to get counts, as it loads all relationship data into memory unnecessarily.

Related: Laravel Collections: Beyond Basic Array Operations | Laravel Events and Listeners: Building Decoupled Applications | Building Multi-tenant Applications with Laravel: A Comprehensive Guide

Share this article

Add Comment

No comments yet. Be the first to comment!

More from Laravel