Don't pay for services you don't use.
Most Laravel service providers instantiate services immediately during registration (eager loading). This means every request pays the cost of every service, even if the request never uses them. For heavy services (e.g., PDF generators, image processors), this is wasted CPU and memory.
| Scenario | Eager Resolution | Lazy Resolution | Improvement |
|---|---|---|---|
| Request using 3 of 20 services | 20 services instantiated | 3 services instantiated | 85% fewer instantiations |
| Heavy service cost (PDF, Image, etc.) | Always paid | Only when used | 100% reduction when not used |
| Memory per request (20 services) | ~2-5 MB | ~0.5-1 MB | 70-80% reduction |
| CPU per request (service creation) | 10-20ms | 2-5ms | 70-80% reduction |
A typical Laravel app with 50 registered services:
// app/Providers/AppServiceProvider.php
public function register(): void
{
// EAGER (BAD) - Instantiates immediately
$this->app->bind(HeavyService::class, function ($app) {
return new HeavyService(
$app->make(Database::class),
$app->make(Logger::class)
);
});
// LAZY (GOOD) - No instantiation yet
$this->app->bind(HeavyService::class, HeavyService::class);
// For services with dependencies, use a closure that returns class name
$this->app->bind(AnotherService::class, function ($app) {
return AnotherService::class; // ← Returns class name, not instance!
});
}
// Bind interface to implementation
$this->app->bind(PaymentGatewayInterface::class, StripeGateway::class);
// StripeGateway is NOT instantiated until requested
$this->app->bind(NotificationInterface::class, SmsNotification::class);
// SmsNotification is NOT instantiated until requested
// When you resolve the interface:
$payment = app(PaymentGatewayInterface::class);
// NOW StripeGateway is instantiated (lazy)
// Singleton with lazy resolution
$this->app->singleton(ExpensiveService::class, function ($app) {
// This closure only runs when the service is FIRST accessed
return new ExpensiveService(
$app->make(Database::class),
$app->make(Cache::class)
);
});
// Even singletons can be lazy! The service is only created once,
// but that creation happens on FIRST use, not on every request.
class ReportGenerator
{
public function __construct(
private Database $db,
private Cache $cache,
private Logger $logger,
private Mailer $mailer
) {}
}
// EAGER (BAD)
$this->app->bind(ReportGenerator::class, function ($app) {
return new ReportGenerator(
$app->make(Database::class), // Instantiated now
$app->make(Cache::class), // Instantiated now
$app->make(Logger::class), // Instantiated now
$app->make(Mailer::class) // Instantiated now
);
}); // All 5 services created even if ReportGenerator not used!
// LAZY (GOOD)
$this->app->bind(ReportGenerator::class, function ($app) {
// Return a closure that will be resolved later
return function () use ($app) {
return new ReportGenerator(
$app->make(Database::class),
$app->make(Cache::class),
$app->make(Logger::class),
$app->make(Mailer::class)
);
};
});
// Usage
$generator = app(ReportGenerator::class); // Returns closure
$instance = $generator(); // Now instantiated!
Laravel's container automatically handles lazy resolution for class names. Just use:
$this->app->bind(ReportGenerator::class, ReportGenerator::class);
Laravel will resolve dependencies lazily when the class is first instantiated.
Your app has heavy services only used by admin users (e.g., PDF export, report generation, user import). But these services are instantiated on EVERY request, including public visitors.
// app/Providers/AppServiceProvider.php
public function register(): void
{
// These services are ONLY needed by admin
// Using lazy resolution = only instantiated on admin requests
$this->app->bind(PdfExportService::class, PdfExportService::class);
$this->app->bind(ReportGenerator::class, ReportGenerator::class);
$this->app->bind(UserImportService::class, UserImportService::class);
$this->app->bind(BulkEmailService::class, BulkEmailService::class);
}
// Admin controller (only admin routes use these)
class AdminReportController
{
public function export(ReportGenerator $generator)
{
// ReportGenerator is instantiated NOW (only on admin requests)
// Public visitors never pay this cost
return $generator->generate();
}
}
Result: Public visitors (90% of traffic) don't pay the cost of admin-only services. 90% reduction in service instantiation overhead.
Laravel also supports lazy collections for database queries:
// EAGER (loads all at once - heavy memory)
$users = User::all(); // 100k users → 500MB memory
// LAZY (loads one at a time - memory efficient)
$users = User::cursor(); // 100k users → 5MB memory
foreach ($users as $user) {
// Process one user at a time
}
// Lazy Collection with chunking
$users = User::lazy(); // Similar to cursor
| Aspect | Eager Resolution | Lazy Resolution |
|---|---|---|
| Instantiation timing | During service registration | On first use | Memory per request | All services | Only used services | CPU per request | All service constructors | Only used service constructors | Best for | Core services (used everywhere) | Optional/additional services |
Topic 24: Observers & withoutEvents — The silent performance killers. Why every model event triggers observers. How to disable events for bulk operations. When to move logic to queues.