PHP Laravel Framework Interview Questions 2026: Eloquent, Queues and Architecture Patterns
Master Laravel 13 interview questions covering Eloquent ORM relationships, queue architecture, service container patterns, and real-world coding challenges that senior developers face in 2026.

PHP Laravel framework interview questions test more than syntax knowledge. Companies hiring in 2026 expect candidates to explain Eloquent relationship optimization, queue failure handling, and service container bindings with production-level detail. Laravel 13 introduces AI tooling, JSON:API resources, and vector search capabilities that add new dimensions to technical interviews.
Senior Laravel positions focus on three areas: Eloquent performance (N+1 prevention, chunking, cursor pagination), queue reliability (retries, dead letter handling, job batching), and architectural decisions (service providers, facades vs injection, repository patterns).
Eloquent ORM: Relationship Loading and Query Optimization
Eloquent interview questions consistently target the N+1 problem. Candidates must demonstrate when to use with(), load(), and loadMissing(), plus explain the memory tradeoffs of eager loading large datasets.
// Eager load with constraints to avoid loading unnecessary data
$users = User::query()
->with(['posts' => function (Builder $query) {
$query->where('published', true)
->select('id', 'user_id', 'title', 'published_at');
}])
->withCount('posts')
->paginate(25);
// Bad: triggers N+1 when accessing posts in loop
foreach ($users as $user) {
$user->posts; // Each iteration queries the database
}The withCount() method adds a subquery that counts related records without loading them. This avoids memory overhead when displaying counts in listing pages. For relationships that might not be accessed, loadMissing() prevents redundant queries when the relationship was already eager loaded upstream.
A common follow-up question asks about cursor pagination vs offset pagination. Cursor pagination uses cursorPaginate() and avoids the performance cliff that offset pagination hits on large tables. The tradeoff: cursor pagination cannot jump to arbitrary pages.
Advanced Eloquent: Polymorphic Relationships and Query Scopes
Polymorphic relationships require precise understanding of the morph map and index strategy. Interviewers often present a scenario with comments that belong to posts, videos, and products, then ask candidates to optimize the queries.
class Comment extends Model
{
// morphTo automatically resolves the parent type from commentable_type column
public function commentable(): MorphTo
{
return $this->morphTo();
}
}
// AppServiceProvider.php
// Morph map prevents class name exposure in database and improves query performance
Relation::enforceMorphMap([
'post' => Post::class,
'video' => Video::class,
'product' => Product::class,
]);The morph map serves two purposes: it decouples database values from class names (enabling refactoring without migrations), and it creates shorter, indexable strings. Without a morph map, Laravel stores full class names like App\Models\Post in the commentable_type column.
Query scopes demonstrate understanding of reusable query logic. Global scopes apply automatically, while local scopes require explicit invocation. A typical interview question asks when to use each.
// Global scope: applies to all queries unless explicitly removed
protected static function booted(): void
{
static::addGlobalScope('published', function (Builder $builder) {
$builder->where('published', true);
});
}
// Local scope: invoked explicitly with ->popular()
public function scopePopular(Builder $query, int $minViews = 1000): Builder
{
return $query->where('views', '>=', $minViews);
}
// Usage: Post::popular(5000)->get()
// Remove global scope: Post::withoutGlobalScope('published')->get()Queue Architecture: Job Design and Failure Handling
Queue questions assess production readiness. Laravel 13.31 added Queue::totalSize() to count every job on a connection and a JobInterrupted event for graceful shutdown handling. Candidates should know the difference between sync, database, Redis, and SQS drivers, plus when each fits.
For deeper coverage of Laravel queue patterns, see the Queues & Jobs interview questions module.
class ProcessOrder implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3; // Maximum attempts before failing
public int $backoff = 60; // Seconds between retries
public int $timeout = 120; // Maximum execution time
public int $maxExceptions = 2; // Fail after 2 unhandled exceptions
public function __construct(
public readonly Order $order
) {}
public function handle(PaymentGateway $gateway): void
{
// SerializesModels refreshes the Order from database when job runs
$gateway->charge($this->order);
}
public function failed(Throwable $exception): void
{
// Called after all retries exhausted - alert team, refund, etc.
Log::critical('Order processing failed', [
'order_id' => $this->order->id,
'exception' => $exception->getMessage(),
]);
}
}The SerializesModels trait stores only the model ID in the queue payload, not the full model. When the job runs, Eloquent fetches a fresh instance. This prevents stale data but means deleted models cause ModelNotFoundException. Handle this with deleteWhenMissingModels.
Job batching questions appear frequently. Batches allow grouping jobs and defining callbacks for completion, failure, or cancellation.
public function processBatch(array $orderIds): PendingBatch
{
$jobs = collect($orderIds)
->map(fn (int $id) => new ProcessOrder(Order::find($id)));
return Bus::batch($jobs)
->name('Process Orders ' . now()->toDateString())
->allowFailures() // Continue batch even if some jobs fail
->then(function (Batch $batch) {
// All jobs completed successfully
Notification::send(Admin::all(), new BatchCompleted($batch));
})
->catch(function (Batch $batch, Throwable $e) {
// First failure in batch
Log::warning('Batch job failed', ['batch_id' => $batch->id]);
})
->finally(function (Batch $batch) {
// Batch finished (success or failure)
})
->dispatch();
}Ready to ace your Laravel interviews?
Practice with our interactive simulators, flashcards, and technical tests.
Service Container: Bindings, Contextual Injection, and Providers
The service container is Laravel's core. Interview questions test understanding of binding types, resolution order, and when to use each approach.
public function register(): void
{
// Singleton: same instance throughout request lifecycle
$this->app->singleton(PaymentGateway::class, function (Application $app) {
return new StripeGateway(
apiKey: config('services.stripe.secret'),
logger: $app->make(LoggerInterface::class)
);
});
// Bind: new instance on each resolution
$this->app->bind(ReportGenerator::class, function (Application $app) {
return new PdfReportGenerator($app->make(ViewFactory::class));
});
// Contextual binding: different implementation per consumer
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(fn () => Storage::disk('photos'));
$this->app->when(DocumentController::class)
->needs(Filesystem::class)
->give(fn () => Storage::disk('documents'));
}Contextual bindings solve the problem of injecting different implementations into different classes without creating separate interfaces. The container resolves Filesystem differently based on which class requests it.
A senior-level question asks about deferred providers. Standard service providers register on every request, even if their services go unused. Deferred providers register only when one of their declared services is resolved.
class ReportingServiceProvider extends ServiceProvider implements DeferrableProvider
{
public function register(): void
{
$this->app->singleton(ReportingService::class, function () {
return new ReportingService(/* heavy initialization */);
});
}
// Container only loads this provider when ReportingService is requested
public function provides(): array
{
return [ReportingService::class];
}
}Middleware Architecture: Request Pipeline and Terminable Middleware
Middleware interview questions focus on the request/response lifecycle, priority ordering, and terminable middleware for post-response tasks.
class RateLimitApi
{
public function handle(Request $request, Closure $next): Response
{
$key = 'api:' . ($request->user()?->id ?? $request->ip());
if (RateLimiter::tooManyAttempts($key, maxAttempts: 60)) {
$retryAfter = RateLimiter::availableIn($key);
return response()->json(
['error' => 'Rate limit exceeded'],
Response::HTTP_TOO_MANY_REQUESTS
)->header('Retry-After', $retryAfter);
}
RateLimiter::hit($key, decayMinutes: 1);
// Pass request to next middleware
$response = $next($request);
// Modify response before returning to client
return $response->header(
'X-RateLimit-Remaining',
RateLimiter::remaining($key, 60)
);
}
}The $next($request) call passes the request down the middleware stack. Code before this call runs during the request phase; code after runs during the response phase. This symmetry enables logging, timing, and response modification.
Terminable middleware executes after the response is sent to the client, useful for slow operations that should not delay the user.
class LogSlowRequests implements TerminableMiddleware
{
public function handle(Request $request, Closure $next): Response
{
$request->attributes->set('start_time', microtime(true));
return $next($request);
}
public function terminate(Request $request, Response $response): void
{
$duration = microtime(true) - $request->attributes->get('start_time');
if ($duration > 1.0) {
Log::warning('Slow request detected', [
'url' => $request->fullUrl(),
'duration_ms' => round($duration * 1000),
'user_id' => $request->user()?->id,
]);
}
}
}Repository Pattern: When to Use and When to Avoid
The repository pattern generates debate. Laravel's Eloquent already implements Active Record. Adding repositories creates abstraction that some teams find unnecessary. Interviewers ask candidates to justify their position.
Arguments for repositories:
- Decouples business logic from Eloquent, enabling database switches (rare in practice)
- Simplifies testing by mocking the repository interface
- Centralizes query logic when multiple controllers use similar queries
Arguments against:
- Adds boilerplate without clear benefit for CRUD operations
- Eloquent's query builder is already expressive and testable
- Laravel's dependency injection works directly with models
class OrderRepository
{
public function __construct(
private readonly Order $model
) {}
public function findPendingForUser(User $user): Collection
{
return $this->model->query()
->where('user_id', $user->id)
->where('status', OrderStatus::Pending)
->with('items.product')
->orderByDesc('created_at')
->get();
}
public function calculateRevenueForPeriod(CarbonPeriod $period): Money
{
$total = $this->model->query()
->whereBetween('completed_at', [$period->start, $period->end])
->where('status', OrderStatus::Completed)
->sum('total_cents');
return Money::ofMinor($total, 'USD');
}
}A balanced answer: repositories add value when query logic is complex, shared across contexts, or when the team prioritizes testability through interface mocking. For simple CRUD, injecting the model directly keeps code concise.
Testing: Feature Tests, Mocking, and Database Strategies
Laravel testing questions verify candidates can write reliable, fast tests. The framework provides RefreshDatabase, DatabaseTransactions, and LazilyRefreshDatabase traits with different tradeoffs.
class OrderTest extends TestCase
{
use RefreshDatabase; // Migrates once per class, transactions per test
public function test_user_can_place_order(): void
{
// Arrange
$user = User::factory()->create();
$product = Product::factory()->create(['price_cents' => 2999]);
// Act
$response = $this->actingAs($user)
->postJson('/api/orders', [
'items' => [
['product_id' => $product->id, 'quantity' => 2],
],
]);
// Assert
$response->assertCreated()
->assertJsonPath('data.total_cents', 5998);
$this->assertDatabaseHas('orders', [
'user_id' => $user->id,
'status' => 'pending',
]);
}
public function test_order_dispatches_processing_job(): void
{
Queue::fake();
$user = User::factory()->create();
$order = Order::factory()->for($user)->create();
$this->actingAs($user)
->postJson("/api/orders/{$order->id}/process");
Queue::assertPushed(ProcessOrder::class, function ($job) use ($order) {
return $job->order->id === $order->id;
});
}
}The RefreshDatabase trait runs migrations once per test class and wraps each test in a transaction that rolls back. This balances speed with isolation. For large test suites, LazilyRefreshDatabase skips migration if the schema matches.
Mocking external services prevents tests from hitting real APIs.
public function test_payment_failure_returns_error(): void
{
$gateway = $this->mock(PaymentGateway::class);
$gateway->shouldReceive('charge')
->once()
->andThrow(new PaymentFailedException('Card declined'));
$user = User::factory()->create();
$order = Order::factory()->for($user)->create();
$response = $this->actingAs($user)
->postJson("/api/orders/{$order->id}/pay");
$response->assertStatus(402)
->assertJsonPath('error', 'Card declined');
}Start practicing!
Test your knowledge with our interview simulators and technical tests.
Laravel 13 Features Likely to Appear in Interviews
Laravel 13 shipped March 2026 with features that interviewers now expect candidates to know. The official release notes cover these in detail.
AI SDK: Laravel 13 includes first-party AI tooling with a unified API for text generation, embeddings, and tool-calling agents. Questions may ask about integrating AI features into existing applications.
JSON:API Resources: The new JsonApiResource class simplifies building JSON:API-compliant responses with relationships, sparse fieldsets, and compound documents.
Vector Search: The AsVector Eloquent cast handles vector columns for semantic search. This works with PostgreSQL pgvector and MariaDB's binary vector format.
Cloud Facade: Cloud::hosted() detects Laravel Cloud deployment, Cloud::usesManagedQueues() checks for managed queue connections. This enables environment-specific behavior without configuration checks.
Key Takeaways for Laravel Interview Preparation
- Explain N+1 prevention with
with(),loadMissing(), andwithCount(). Know the memory implications of eager loading large datasets. - Describe queue job design: retry strategies,
SerializesModelsbehavior, batching callbacks, and when to usedeleteWhenMissingModels. - Articulate service container binding types: singleton vs bind, contextual bindings, and deferred providers for performance.
- Demonstrate middleware request/response flow and terminable middleware for post-response operations.
- Take a position on the repository pattern with concrete tradeoffs rather than dogmatic rules.
- Write tests that use
RefreshDatabaseappropriately, mock external services, and assert queue dispatches. - Stay current on Laravel 13 features, particularly AI SDK, JSON:API resources, and vector search capabilities.
Can you spot the bug in Laravel?
One real snippet, one hidden bug, one attempt a day. No account needed to try.

Written by
Anthony Fillion-MailletFounder of SharpSkill
Full-stack developer for over 10 years. Runs SharpSkill and answers for everything published here.
Updated on September 16, 2026
Tags
Share
Related articles

PHP Laravel Developer Interview Questions 2026: Complete Preparation Guide
Master Laravel interview questions for 2026. From Eloquent ORM and Service Container to queues, testing, and Laravel 13 features, prepare for technical interviews with real-world examples.

Laravel and PHP Interview Questions: Top 25 in 2026
The 25 most common Laravel and PHP interview questions. Eloquent ORM, middleware, artisan, queues, tests and architecture with detailed answers and code examples.

Laravel Queues and Jobs: Asynchronous Architecture and Interview Questions 2026
Deep dive into Laravel queues and jobs architecture. Covers job dispatching, batching, chaining, middleware, failed job handling, and queue worker management with Laravel 12 examples.