Beginner Level (0–1 Years)
1. Explain how the service container differs from the facade pattern in Laravel. What mistake do beginners often make when using facades?
Answer:
The service container is Laravel’s dependency injection system, managing class bindings and resolving instances at runtime via constructor or method injection. A facade provides a static interface to access objects resolved from the container. Beginners often mistake facades for static singletons, not realizing they resolve regular objects that can be mocked or replaced via container bindings (e.g., bind()
or instance()
) for testing.
2. When would you prefer Route::resource() over manual route declarations – and when might you avoid it?
Answer:
Use Route::resource()
for standard CRUD endpoints (index
, store
, show
, update
, destroy
, etc.) to keep routes concise and naming consistent. You can limit actions with only
or except
options. Avoid it when needing non-RESTful URIs, custom HTTP verbs, a subset of actions not easily constrained, or complex nested paths for public APIs.
3. A colleague uses DB::table(‘users’)->get() everywhere because it’s faster. Is this always true compared with Eloquent?
Answer:
No. Query Builder (DB::table
) skips model hydration, offering a marginal speed advantage for simple, read-only queries with large datasets. However, Eloquent’s clarity, relationships, accessors, and scopes often outweigh this overhead. With caching (e.g., remember()
), performance differences are negligible, as the database query—not hydration—is the main bottleneck.
4. What will User::all() do in a memory-constrained job, and how can you avoid problems?
Answer:
User::all()
runs SELECT *
and loads all rows into memory, potentially crashing a queue worker or CLI command due to RAM exhaustion. Use chunk()
, chunkById()
, cursor()
, or lazy()
to stream results, processing only a subset of rows at a time.
5. Describe two ways Laravel prevents CSRF attacks. What manual step must you do in an AJAX request to benefit?
Answer:
(1) The VerifyCsrfToken
middleware validates a _token
field or X-CSRF-TOKEN
header on state-changing requests. (2) Laravel regenerates the token on login/logout to prevent replay attacks. For AJAX, include the CSRF token in the X-CSRF-TOKEN
header or as _token
in the request body.
6. What’s the difference between softDelete(), forceDelete(), and delete() on an Eloquent model?
Answer:
softDelete()
is not a real method; soft deletion occurs via delete()
when the SoftDeletes
trait is used, setting deleted_at
. Without the trait, delete()
permanently removes the row. forceDelete()
always bypasses soft deletion for permanent deletion.
7. In a Blade view, someone writes {{ $user->password }} to help debug. Why is this potentially dangerous even in local?
Answer:
Displaying passwords, even hashed, risks leaking them into logs, compiled views, or version control if the code is committed. Attackers could brute-force hashes offline, violating security policies and user trust, even in local environments.
8. Explain the N+1 problem in Eloquent and give two ways Laravel helps detect or fix it.
Answer:
The N+1 problem occurs when loading N parent models and lazily accessing a relationship, triggering N additional queries. Fix it with eager loading via with()
or load()
. Detect it using Telescope, Laravel Debugbar, \DB::enableQueryLog()
, or Laravel’s strict mode (Laravel 10/11), which throws exceptions for N+1 issues.
9. Why might php artisan route:cache break a closure-based route that worked before?
Answer:
Route caching serializes the route table, but PHP cannot serialize closures, causing closure-based routes to be omitted and return 404. Replace closures with controller methods (preferred in production) or avoid route caching in environments using closures.
10. How does Laravel’s configuration cache differ from plain config files in production?
Answer:
php artisan config:cache
merges all config files and .env
into a single PHP array, reducing file-system reads and parsing, especially beneficial with many config files. Changes to .env
or configs are ignored until the cache is rebuilt with config:cache
.
11. You see $this->call() inside a database seeder. What does it do, and why might the order matter?
Answer:
$this->call(OtherSeeder::class)
synchronously runs another seeder class. Order matters because later seeders may depend on data (e.g., foreign keys or IDs) created by earlier ones, such as seeding roles before users.
12. Give an example where using a job instead of an event listener is the right choice, even though both can be queued.
Answer:
A nightly cleanup or report generation task, triggered by the scheduler, suits a standalone job since no domain event (e.g., OrderShipped
) exists. Event listeners are better for reacting to multiple domain events.
13. What will happen if you forget to list both $fillable and $guarded in a model and then call create($request->all())?
Answer:
With an empty $fillable
and default empty $guarded
, Laravel prevents mass assignment, throwing a MassAssignmentException
to protect against over-posting attacks.
14. How can a migration be re-run on production if php artisan migrate:refresh is unsafe?
Answer:
Use php artisan migrate:rollback --step=1
then php artisan migrate
to re-apply the last migration, or create a new “fix” migration to alter the table without data loss. Avoid migrate:fresh
, as it drops all tables.
15. Why doesn’t a model with public $timestamps = false; automatically update updated_at?
Answer:
Setting $timestamps = false
disables automatic updates for created_at
and updated_at
. To update these fields, set $timestamps = true
and manage them manually or let Eloquent handle them.
16. Explain the difference between ->pluck(‘id’) and ->select(‘id’)->get()—both performance and structure.
Answer:
pluck('id')
fetches a collection of scalar IDs without model hydration, limited to one or two columns. select('id')->get()
hydrates full model instances (with only id
set), making it slower and heavier but more flexible for complex queries.
17. How do you create a custom middleware in Laravel, and what is a common use case for it?
Answer:
Use php artisan make:middleware EnsureUserIsActive
to generate a middleware class. In the handle
method, add logic to check conditions (e.g., if (auth()->user()->is_active) { return $next($request); }
) and return a response like abort(403)
if the condition fails. Register it in app/Http/Kernel.php
as a global, route, or group middleware. A common use case is restricting access to routes for inactive users or specific roles.
18. Which Artisan command clears compiled Blade templates, and why might you run it after changing view-related env variables?
Answer:
php artisan view:clear
clears compiled Blade templates. Run it after changing view-related env variables (e.g., theme paths) to remove stale templates, then use view:cache
to recompile them.
19. What is Laravel’s Form Request validation, and how does it differ from validating in a controller?
Answer:
Form Request validation uses a custom request class (created via php artisan make:request StorePostRequest
) to define validation rules in the rules()
method (e.g., return ['title' => 'required|string|max:255'];
). It separates validation logic from controllers, improving reusability and testability. Unlike controller-based validation (e.g., $request->validate()
), Form Requests can include custom logic in authorize()
and custom error messages.
20. What does nullableMorphs(‘imageable’) generate in a migration?
Answer:
nullableMorphs('imageable')
creates two nullable columns: imageable_id
(unsigned big integer) and imageable_type
(string) for polymorphic relations, typically with an imageable_id_imageable_type_index
index, though the index is optional.
21. How can you eager-load counts of a relationship in one query?
Answer:
Use withCount('comments')
to add a comments_count
column via a subquery, without loading the comments. You can alias or constrain it (e.g., withCount(['comments as unanswered_comments_count' => fn($q) => $q->whereNull('answered_at')])
). Note that subqueries may not work with all database drivers.
22. Why might app()->environment(‘local’) be preferred over config(‘app.env’) === ‘local’ inside a package?
Answer:
app()->environment('local')
reads from APP_ENV
or the app’s configuration, works with multiple environments (environment(['local', 'testing'])
), and functions when configs are cached. Packages should avoid assuming app.env
exists or that configs are uncached.
23. How are model factories used in Laravel, and why are they helpful for testing or seeding?
Answer:
Model factories, defined in database/factories/
(e.g., UserFactory.php
), generate fake data for models using fakerphp/faker
(e.g., 'name' => $this->faker->name
). Create them with php artisan make:factory UserFactory
. Use them in tests or seeders (e.g., User::factory()->count(10)->create()
) to populate the database. They simplify testing by providing consistent, realistic data and support seeding for development or demo environments.
24. You add @extends(‘layouts.app’) but render the view from a queued job. Why might it fail, and what fix exists?
Answer:
Queued jobs lack the HTTP lifecycle (no session, CSRF, or user), breaking Blade templates expecting them. Render the view before queuing, pass only necessary data, or serialize data for the job. Using the sync
queue is an option but may not suit heavy jobs.
25. Laravel 11 removed the app/Http/Controllers/Auth scaffolding. How would you generate auth views now?
Answer:
Install a starter kit like Breeze: composer require laravel/breeze --dev
, then php artisan breeze:install
(or use Jetstream/Fortify for more features). Run npm install && npm run build
for frontend assets (e.g., Vite) and php artisan migrate
for views and migrations.
👋 Need top Laravel developers for your project? Interview this week!
Fill out the form to book a call with our team. We’ll match you to the top developers who meet your requirements, and you’ll be interviewing this week!
Intermediate Level (1–3 Years)
1. What is a Service Provider in Laravel, and why is the register() method called before boot() during application startup?
Answer:
A service provider is the central place to configure and bind container services. Laravel calls register()
first to ensure all bindings are available in the container before any provider’s boot()
runs. Once all services are registered, boot()
executes, allowing logic like event listeners or route registration to resolve dependencies from the fully-populated container.
2. How would you add a global Eloquent scope that automatically limits all queries to the tenant currently identified in the request?
Answer:
Create a scope class implementing Illuminate\Database\Eloquent\Scope
:
class TenantScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
if ($tenantId = request()->tenant_id ?? auth()->user()?->tenant_id) {
$builder->where($model->getTable().'.tenant_id', $tenantId);
}
}
}
Use a trait with protected static function booted() { static::addGlobalScope(new TenantScope); }
to apply it to tenant-aware models. This ensures the scope only applies when a tenant ID is available, avoiding errors in non-request contexts.
3. Describe how unsignedBigInteger foreign keys can still break on some MySQL versions when using migrate:fresh, and how to avoid it.
Answer:
With UTF8mb4 collations, MySQL’s index length limit (767 bytes in older versions) can cause foreign key creation to fail if string columns are too long. Set Schema::defaultStringLength(191);
in AppServiceProvider::boot()
to ensure compatibility. Alternatively, use php artisan schema:dump
to generate a schema file that avoids runtime collation issues.
4. What is the difference between php artisan queue:work and php artisan queue:listen and why is queue:work generally preferred in production?
Answer:
queue:listen
forks a new process per job, reloading the framework for instant code updates but with high overhead. queue:work
reuses a single process for multiple jobs, minimizing bootstrap time and memory use, making it ideal for production where code stability is prioritized.
5. Explain the purpose of the retryUntil() method on a queued job and give a practical example.
Answer:
retryUntil()
defines a timestamp after which a job stops retrying, even if attempts remain (default: 3 tries). Example: public function retryUntil() { return now()->addMinutes(30); }
for an email job ensures retries stop 30 minutes after the first failure, useful when the action (e.g., order confirmation) becomes irrelevant.
6. How does route model binding resolve soft-deleted models when SoftDeletes is used, and how can you include them?
Answer:
Implicit and explicit bindings exclude soft-deleted rows by default. To include them, use withTrashed()
: Route::bind('post', fn ($value) => Post::withTrashed()->where('slug', $value)->firstOrFail());
. This retrieves both active and soft-deleted models.
7. What is Broadcast::routes() used for, and how would you protect a private channel named orders.{id} so users can only listen to their own order updates?
Answer:
Broadcast::routes()
registers the `/broadcasting/auth` endpoint for authenticating WebSocket channels. Protect a private channel in routes/channels.php
:
Broadcast::channel('orders.{id}', function ($user, $id) {
return $user->id === Order::findOrFail($id)->user_id;
});
This ensures only the order’s owner can subscribe.
8. Why can executing a long-running DB::transaction() block other database connections, and how can you mitigate that?
Answer:
InnoDB’s row-level locks persist during a transaction. If the closure includes slow operations (e.g., HTTP calls), locks block other queries. Mitigate by minimizing the transaction scope to only database operations or using FOR SHARE
to reduce lock contention.
9. What advantage do custom enum casts provide over traditional string columns, and how do you define one in Laravel 10?
Answer:
Enums provide type safety and IDE autocompletion. Use an `enum` column in the migration: $table->enum('status', ['draft', 'published']);
. Define the enum:
enum Status: string
{
case Draft = 'draft';
case Published = 'published';
}
class Post extends Model
{
protected $casts = ['status' => Status::class];
}
This validates assignments and returns enum instances for $post->status
.
10. You created a FormRequest class with authorize() returning false. What HTTP response will the user see and why is that useful?
Answer:
A 403 Forbidden response (JSON for APIs, redirect for web) is returned. This centralizes authorization logic, preventing validation or controller execution for unauthorized requests, improving security and maintainability.
11. What is the difference between rate:limit middleware and ThrottleRequests facade usage when building custom rate limiters in Laravel 10?
Answer:
rate:limit
is a shorthand for the throttle
middleware, referencing a named limiter defined in RouteServiceProvider
via RateLimiter::for()
. ThrottleRequests
is the underlying middleware class. Configure custom limits with RateLimiter::for('api', fn () => Limit::perMinute(60));
and apply via Route::middleware('throttle:api')
.
12. How can you stop a queued job from retrying if a non-recoverable exception occurs, yet still allow retries for other exceptions?
Answer:
In handle()
, catch non-recoverable exceptions (e.g., InvalidPaymentException
) and call $this->fail($exception);
. Set public $tries = 3;
to allow retries for other exceptions. Example: try { ... } catch (InvalidPaymentException $e) { $this->fail($e); }
.
13. Why might calling config([‘mail.mailers.smtp.password’ => ‘secret’]) inside a request be problematic in Octane or Swoole?
Answer:
Octane reuses the config array across requests. Runtime changes persist, affecting subsequent requests and causing security issues. Use a transient mailer instance or bind custom credentials in the container for the request.
14. Explain how Laravel’s cache:tags feature works and mention a driver that does not support it.
Answer:
Tagged caches store item keys in a set per tag. Flushing a tag deletes all associated keys. Supported by Redis and Memcached; the file
driver does not support tags due to its lack of set-based operations.
15. You push thousands of jobs per minute and notice Redis memory usage exploding. Which Laravel feature helps and how?
Answer:
Enable queue:prune-batches
in the scheduler or set queue.prune_batches
config. It removes old batch, failed-job, and Horizon records, freeing Redis memory by deleting associated keys.
16. Give two reasons why a query using orWhere() inside a whereHas() closure might not work as intended.
Answer:
(1) Without grouping orWhere
in parentheses, it may alter the parent query’s logic. (2) Placing orWhere
outside the closure applies it to the main model, not the relationship, yielding incorrect results.
17. What’s the difference between assertDatabaseHas() and assertModelExists() in a feature test?
Answer:
assertDatabaseHas()
checks raw table data without needing a model instance. assertModelExists()
verifies a model by its primary key, failing for soft-deleted rows unless withTrashed()
is used.
18. How can you prevent an accidental mass assignment on attributes dynamically created by setAttribute()?
Answer:
Use protected $guarded = ['dynamic_field'];
or define $fillable
. For JSON columns, cast to `array` and validate sub-keys in a mutator, ensuring only allowed fields are set.
19. Explain how to use Lazy Collections with chunking so you can pipe a million-row CSV export directly to a stream response.
Answer:
return Response::stream(function () {
User::lazyById(1000)
->each(function ($user) {
echo implode(',', [$user->id, $user->email]).PHP_EOL;
});
}, 200, ['Content-Type' => 'text/csv']);
lazyById()
batches by primary key, minimizing memory usage while streaming rows to the client.
20. Briefly describe job chaining and mention one limitation compared with batch processing.
Answer:
Job chaining runs jobs sequentially, with each dispatched only after the previous succeeds (e.g., Job1::dispatch()->chain([new Job2])
). Unlike batch processing, chaining doesn’t support parallel execution or progress tracking.
21. What Blade directive helps avoid XSS when inserting untrusted Markdown rendered through Parsedown?
Answer:
Use a package like spatie/laravel-dompurify
with a custom directive (e.g., @clean($html)
) to sanitize HTML, stripping unsafe tags. Alternatively, use {!! Str::markdown($text, ['html_input' => 'strip']) !!}
for basic escaping.
22. How does Gate::before() differ from Gate::after() in a policy?
Answer:
Gate::before()
runs before ability checks, short-circuiting if non-null. Gate::after()
runs after, modifying only if the ability check allows; it can’t override a false
from before()
.
23. Describe the tap() helper and give a practical Eloquent example.
Answer:
tap()
executes a callback on a value and returns it. Example:
$user = tap(User::create($data), fn ($u) => $u->assignRole('subscriber'));
This assigns a role and returns the model for further use.
24. What is laravel/pint and why might you integrate it into CI instead of using php-cs-fixer directly?
Answer:
Pint is Laravel’s wrapper for php-cs-fixer
with a predefined style. It requires no configuration, aligns with Laravel’s standards, and offers a --test
flag for CI to fail on style violations.
25. What issue arises if you call bcrypt() directly on a password every time a user updates their profile, even if the password was unchanged?
Answer:
Re-hashing a hashed password creates a new hash, invalidating the user’s login. Check Hash::needsRehash($password)
or verify $request->filled('password')
before hashing.
26. Explain #[Aware] properties in Blade components.
Answer:
The #[Aware]
attribute marks a public property in a component class to receive data from a parent’s slot, avoiding prop-drilling. Example: #[Aware] public $user;
allows a nested component to access $user
without passing it through intermediate components.
27. How would you conditionally register a middleware group only when the application runs in the api guard?
Answer:
In AppServiceProvider::boot()
, check config('auth.defaults.guard') === 'api'
before registering: if (config('auth.defaults.guard') === 'api') { Route::middlewareGroup('api', [...]); }
. This ensures the group applies only for API contexts.
28. What’s the difference between collect() and Collection::make() when creating custom collection macros?
Answer:
collect()
returns an Illuminate\Support\Collection
. Collection::make()
respects custom collection subclasses (e.g., UserCollection
), preserving macros defined in the subclass.
29. Why could using public_path() in a Laravel Vapor function break file access?
Answer:
Vapor’s AWS Lambda environment is read-only; public_path()
points to an unwritable directory. Use S3 or /tmp
for file storage in Vapor applications.
30. How do you create a custom accessor and mutator in Eloquent, and when might you use them over casts?
Answer:
Define an accessor with get{Attribute}Attribute
(e.g., getFullNameAttribute
) and a mutator with set{Attribute}Attribute
(e.g., setFullNameAttribute
). Example: public function getFullNameAttribute() { return $this->first_name . ' ' . $this->last_name; }
. Use them for complex logic (e.g., concatenating fields or transforming data) that can’t be handled by simple casts like `array` or `enum`. Unlike casts, accessors/mutators allow dynamic computation without altering the database schema.
31. How does the enum database column type in Postgres differ from using a string with a PHP enum cast?
Answer:
Postgres’ enum
enforces values at the database level, using less storage. However, adding values requires ALTER TYPE
, which may lock the table. A string
with PHP enum cast allows new cases without migrations but lacks DB-level validation.
32. What problem does Schema::disableForeignKeyConstraints() solve in tests, and what risk does it introduce?
Answer:
It allows truncating tables without FK order concerns, speeding up tests. However, it risks missing FK violations that production would enforce, potentially allowing invalid data in tests.
33. Describe how you would mock an HTTP call using Laravel’s Http::fake() so that only requests to api.stripe.com are faked.
Answer:
Http::fake([
'api.stripe.com/*' => Http::response(['id' => 'tok_123'], 200),
]);
Non-matching URLs proceed to real HTTP requests.
34. Why might you choose json columns for storing a user’s dynamic preferences instead of a separate table, and what Eloquent feature helps access nested data easily?
Answer:
JSON columns avoid join overhead for sparse data. Use protected $casts = ['settings' => 'array'];
to access nested keys like $user->settings['dark_mode']
and query with where('settings->dark_mode', true)
.
35. How can you enforce that queued jobs are processed only after a specific Horizon tag’s current load drops below a threshold?
Answer:
Create a job middleware checking Horizon::workload()
. If the tag’s load exceeds the threshold, return $this->release(30);
to delay; otherwise, proceed with the job.
36. Explain building a custom validation rule that depends on two request fields, starts_at and ends_at.
Answer:
Create an invokable rule: php artisan make:rule DateRangeRule
. In passes()
, compare Carbon::parse($value)->gt(Carbon::parse(request()->input('starts_at')))
. Return a custom error in message()
.
37. Provide two ways to eager-load polymorphic relationship counts for multiple morph types in one query.
Answer:
(1) Use withCount()
on each model’s morphMany()
relationship. (2) Use loadMorphCount(['comments' => [Post::class, Comment::class]])
(Laravel 11) to count for multiple morph types in one query.
38. What is a singleton schedule in Laravel’s Task Scheduling and why is it important for cron jobs?
Answer:
withoutOverlapping()
or onOneServer()
ensures only one instance of a scheduled task runs, preventing race conditions if a previous execution overlaps with the next cron tick.
39. Why could using env() helper inside application code break in production?
Answer:
With config:cache
, env()
returns cached values or null
, ignoring the actual environment. Use config()
or define values in config files to avoid this.
40. Give an example where a Resource Collection is preferable to a simple array response in an API.
Answer:
For a paginated `/users` endpoint, use UserResource::collection($users)
to standardize output, hide sensitive fields, and include pagination meta/links, reducing controller logic.
41. How do you update dozens of records with different values in a single SQL query through Eloquent?
Answer:
Use upsert()
: User::upsert($rows, ['id'], ['name', 'email']);
. It generates one INSERT ... ON DUPLICATE KEY UPDATE
(MySQL) or ON CONFLICT
(Postgres) query.
42. Explain how to swap the default queue connection dynamically for a given job class.
Answer:
Set public $connection = 'high';
on the job class to use the `high` connection. Alternatively, dispatch with Job::dispatch()->onConnection('high')
to override dynamically.
43. What does @once do in Blade templates and when is it useful?
Answer:
@once
renders its content only once per request. Use it to include scripts (e.g., a JavaScript library) or initialize a Vue app in a component reused multiple times in a view.
44. Describe a use case for Job Batching with progress callbacks.
Answer:
Batch CSV row imports with jobs: Bus::batch($jobs)->then(fn () => notify('Done'))->catch(fn () => logError())->dispatch()
. Use $batch->progress()
to update a UI via WebSockets.
45. How would you test that an event listener is dispatched after a model is committed to the database?
Answer:
Use Event::fake()
and DatabaseTransactions
trait. Save the model, then call Event::assertDispatched(EventClass::class)
. Ensure the listener uses dispatchAfterCommit
to delay until the transaction commits.
46. Why might loadMissing() be preferable to load() inside a loop?
Answer:
loadMissing()
only loads unpopulated relationships, preventing duplicate queries or overwriting existing data, reducing N+1 issues in loops with conditional loading.
47. What is event sourcing in Laravel, and how can you implement a simple event-sourced model?
Answer:
Event sourcing stores state as events (e.g., `OrderPlaced`, `OrderShipped`). Use spatie/laravel-event-sourcing
. Define events and an aggregate: OrderAggregate::retrieve($uuid)->place($data)->persist();
. Events are recorded and replayed to rebuild state, ideal for audits or complex workflows.
48. Explain the difference between –seed and –seeder=ClassName with php artisan migrate.
Answer:
--seed
runs DatabaseSeeder
. --seeder=ClassName
executes a specific seeder class, bypassing DatabaseSeeder
, useful for seeding specific data in CI.
49. How can you implement API versioning in Laravel, and what are the pros and cons of using URL-based versioning?
Answer:
Use URL prefixes like Route::prefix('v1')->group(...)
in routes/api/v1.php
or parse `Accept` headers via middleware. URL versioning is clear and cache-friendly but increases URL complexity and risks code duplication. Header versioning is cleaner but less intuitive for clients.
50. When would you choose to broadcast on a presence channel instead of a private one?
Answer:
Presence channels include subscriber metadata, enabling features like showing “who’s online” or collaborative cursors. Use them for real-time apps where participant awareness is needed, unlike private channels for restricted access.

Hire Top LATAM Developers: Guide
We’ve prepared this guide that covers benefits, costs, recruitment, and remote team management to a succesful hiring of developers in LATAM.
Fill out the form to get our guide.
Advanced Level (3+ Years)
1. How do contextual bindings differ from tagged bindings in Laravel’s service container, and when would you use each?
Answer:
Contextual binding supplies a specific implementation when resolving a class: $this->app->when(ReportController::class)->needs(Logger::class)->give(FileLogger::class);
. Tagged bindings group services under a tag: $this->app->tag([SentryLogger::class, FileLogger::class], 'loggers');
, resolved via app()->tagged('loggers')
. Use contextual bindings for specific substitutions; use tags for iterating multiple implementations (e.g., notification channels).
2. Show how you would construct a Laravel Pipeline to transform an inbound JSON payload before it reaches a controller.
Answer:
app(Pipeline::class)
->send($request->all())
->through([
TrimStrings::class,
ConvertEmptyStringsToNull::class,
SanitizeInput::class,
])
->thenReturn();
Place in middleware to normalize the payload sequentially via each class’s handle($payload, $next)
.
3. Why should certain events be marked dispatchAfterCommit in high-write workloads, and what happens if you forget?
Answer:
dispatchAfterCommit
delays events until the transaction commits, ensuring listeners see committed data and avoiding deadlocks. Without it, listeners may query uncommitted data or cause deadlocks, and rollbacks orphan listener side-effects.
4. How does Laravel Octane’s long-lived worker model affect singletons declared in register()?
Answer:
Octane persists singletons across requests, risking state leaks (e.g., user data). Use bind()
or $this->app->scoped()
for request-specific bindings to reset state.
5. In Laravel Horizon, compare the simple, auto, and balanced strategies.
Answer:
Simple
uses FIFO for the first available worker. Auto
allocates workers based on queue wait times. Balanced
distributes workers by pending job count. Use balanced for uniform jobs; auto for varied runtimes prioritizing latency.
6. Outline a strategy to run multi-tenant Eloquent models where each tenant has its own database connection.
Answer:
Create a TenantManager
to configure connections: config(['database.connections.tenant' => []);
;
config(['database.connections.tenant'] => ['database' => $tenant->database]);
In AppServiceProvider::boot()
: DB::purge('tenant'); DB::reconnect('tenant');
. Set models to protected $connection = 'tenant';
. Apply a global scope for tenant filtering.
7. How do you implement multi-guard authentication where APIs use sanctum tokens and the web guard uses sessions?
Answer:
In config/auth.php
:
'guards' => [
'web' => ['driver' => 'session', 'provider' => 'users'],
'api' => ['driver' => 'sanctum', 'provider' => 'users'],
]
Use auth:sanctum
middleware for API routes and Auth::guard('web')
or Auth::guard('api')
in controllers. Policies auto-detect the guard.
8. Describe the mail spooling mechanism and how Laravel queues integrate with it to prevent blocking responses.
Answer:
Spooling stores emails in a queue (e.g., Redis) before SMTP. Use Mail::to($user)->queue(new WelcomeMail)
to push to a queue. Horizon workers handle SMTP asynchronously, reducing response time.
9. How can you freeze time in a feature test while still allowing queued jobs to see the same timestamp?
Answer:
Set Carbon::setTestNow($now)
in the test. Use Bus::fake()
and Bus::dispatchSync()
or artisan queue:work --once
to run jobs in the same process, sharing the frozen time.
10. Explain how to build a custom value-object cast that depends on a service from the container.
Answer:
Define a value object with Castable
:
class Money implements Castable {
public static function castUsing(array $args) { return MoneyCast::class; }
In MoneyCast
, implement CastsAttributes
:
class MoneyCast implements CastsAttributes {
public function __construct(private CurrencyFormatter $fmt) {}
}
Use in model: protected $casts = ['amount' => Money::class];
. The container injects dependencies.
11. What pitfalls exist when using Eloquent relationships across different database connections?
Answer:
Joins use the child’s connection, breaking transaction boundaries and FK enforcement. Eager-loading falls back to separate queries. Use events, manual queries, or replicate data to a single connection.
12. List the order of Eloquent model events for a save() that updates an existing row with soft deletes enabled.
Answer:
retrieved → updating → updated → saved → refreshed
. If delete
is called, add deleting → deleted → trashed
for soft deletes.
13. Describe a ghost-table strategy for zero-downtime schema changes on a 100 GB table.
Answer:
Create a new table (users_new
) with the updated schema. Copy data in chunks using INSERT ... SELECT
. Build indexes, then swap in a transaction: RENAME TABLE users TO users_old, users_new TO users;
. Update code post-swap.
14. How do you create a generated column with Laravel’s schema builder, and why might you use one?
Answer:
Schema::table('orders', fn (Blueprint $t) => $t->decimal('total')->storedAs('subtotal + tax'));
Generated columns compute values at write time, enabling indexing and embedding business logic in the database.
15. Show how to attach a Symfony\Component\Console\Helper\ProgressBar to a custom Artisan command that iterates a Cursor.
Answer:
$users = User::cursor();
$bar = $this->output->createProgressBar(User::count());
foreach ($users as $user) {
$bar->advance();
}
$bar->finish();
The cursor streams rows, and the bar provides feedback.
16. Write a custom collection macro named partitionBy that splits items into multiple keyed sub-collections based on a callback.
Answer:
Collection::macro('partitionBy', function (callable $group) {
return $this->reduce(function ($carry, $item) use ($group) {
$key = $group($item);
$carry[$key] = ($carry[$key] ?? collect())->push($item);
return $carry;
}, []);
});
Use: $orders->partitionBy(fn ($o) => $o->status);
.
17. How do you replace Jetstream’s default TOTP implementation with a hardware-key WebAuthn flow?
Answer:
Override Fortify’s two-factor logic by binding a custom TwoFactorAuthenticationProvider
in a provider. Use laravel/webauthn
to register FIDO2 credentials. Update Jetstream’s routes and Blade views to handle WebAuthn challenges, replacing TOTP forms.
18. Contrast implicit and explicit custom validation rules and explain when implicit rules are necessary.
Answer:
Explicit rules skip validation if the field is absent or empty. Implicit rules (implementing ImplicitRule
) validate even for missing fields. Use implicit rules for required_if
or nested array validation where keys may be absent.
19. Build a dynamic RateLimiter that limits login attempts by IP + email hash.
Answer:
RateLimiter::for('login', fn (Request $r) => Limit::perMinute(5)->by(sha1($r->ip().$r->input('email'))));
Apply: Route::post('login')->middleware('throttle:login');
.
20. Outline the steps to write a custom broadcast driver that posts to Kafka.
Answer:
Create KafkaBroadcaster
implementing Broadcaster
with a broadcast
method:
class KafkaBroadcaster implements Broadcaster {
public function __construct(private KafkaProducer $producer) {}
public function broadcast($channels, $event, $payload) {
$this->producer->send('events', compact('channels', 'event', 'payload'));
}
}
Register: Broadcast::extend('kafka', fn ($app, $config) => new KafkaBroadcaster($app->make(KafkaProducer::class)));
. Configure in broadcasting.php
.
21. Describe a key-rotation scheme for Laravel’s APP_KEY without logging users out.
Answer:
Store keys in config('app.keys')
. Override Encrypter
to try each key in decrypt()
, using the latest for encrypt()
. Rotate by adding a new key, removing the oldest after sessions refresh.
22. Implement a queue middleware that rate-limits jobs to 100 per minute globally, regardless of queue workers.
Answer:
class ThrottleJobs
{
public function handle($job, $next)
{
Redis::throttle('global-jobs')->allow(100)->every(60)
->then($next, fn () => $job->release(5));
}
}
Add to job: public function middleware() { return [new ThrottleJobs]; }
.
23. How can you ensure database writes inside a queued job still participate in the HTTP request’s outer transaction?
Answer:
Use Bus::batch()->dispatchAfterCommit()
or listen for AfterResponse
to dispatch jobs post-commit. Alternatively, use the sync
queue connection within the transaction.
24. Why is chunkById() preferred over chunk() for large mutable tables?
Answer:
chunkById()
uses primary key ranges, avoiding skipped or duplicated rows under concurrent writes. chunk()
uses OFFSET
, which is unstable with table modifications.
25. Discuss trade-offs of switching from auto-incrementing integers to UUID primary keys in a high-write MySQL database.
Answer:
Pros: decentralized IDs, sharding ease, count obfuscation. Cons: larger storage, index fragmentation, slower joins. Mitigate with UUID v7 and BINARY(16)
storage.
26. What advantages do event subscriber classes provide over registering many individual listeners?
Answer:
Subscribers group listeners in one class, improve discovery performance, support wildcards, and resolve dependencies once via the constructor, simplifying complex event handling.
27. How would you customise Laravel’s password broker to send magic-link emails instead of reset tokens?
Answer:
Bind a custom MagicLinkTokenRepository
with short-lived tokens. Override Broker::sendResetLink()
to dispatch a MagicLinkMail
. Update config/auth.php
to use the new broker for passwords.users
.
28. Combine job batching and job chaining to import a CSV then post-process the results.
Answer:
Batch ProcessRow
jobs: Bus::batch($jobs)->then(fn () => Bus::chain([new AggregateImport, new SendSummaryEmail])->dispatch())->dispatch();
. Chained jobs run after all rows are processed.
29. Highlight two Vapor deployment considerations for assets and queues.
Answer:
(1) Assets use S3 with versioned URLs (e.g., mix('/js/app.js')
) due to immutable Lambda code. (2) Queues run as Lambda functions; use CloudWatch for monitoring and SQS for dead-letter queues, as Horizon metrics are unavailable.
30. Explain how generator + LazyCollection pairing reduces memory in a complex ETL.
Answer:
A generator yields one record at a time; LazyCollection::make()
applies map()
, filter()
lazily, processing one record in memory, enabling efficient handling of large datasets.
31. How can you implement a circuit breaker pattern in Laravel to handle unreliable external services?
Answer:
Create a CircuitBreaker
class tracking failures in Redis. Open the circuit after 5 failures in 60 seconds, rejecting requests during a cooldown. Use middleware: Http::withMiddleware(new CircuitBreakerMiddleware)->get('api/external')
. This prevents cascading failures.
32. How do you publish customised stubs so artisan make:model generates UUID ids by default?
Answer:
Run php artisan stub:publish
. Edit stubs/model.stub
to set protected $keyType = 'string';
and public $incrementing = false;
. New models use UUIDs by default.
33. Model a many-to-many polymorphic relation (tags applied to either posts or videos) with additional pivot data.
Answer:
Create a taggables
pivot table with tag_id
, taggable_id
, taggable_type
, applied_by
. In Tag
: public function posts() { return $this->morphedByMany(Post::class, 'taggable')->withPivot('applied_by'); }
. Repeat for videos.
34. When integrating GraphQL with Laravel, how do DataLoader patterns solve N+1 issues compared with Eloquent eager-loading?
Answer:
DataLoader batches key lookups across a GraphQL query, deduplicating DB calls. Eloquent eager-loading requires known parent collections, which GraphQL’s dynamic resolvers may not provide, making DataLoader more flexible.
35. Show a type-safe enum cast for a Postgres status column using PHP 8.2 enum with cases mapping to DB values.
Answer:
Migration:
DB::statement('CREATE TYPE status AS ENUM ("draft", "pub")');
Schema::create('posts', fn ($t) => $t->enum('status', ['draft', 'pub']));
Model:
enum Status: string { case Draft = 'draft'; case Published = 'pub'; }
class Post extends Model { protected $casts = ['status' => Status::class]; }
36. Explain Livewire’s hydration cycle and why expensive queries should be deferred to mount().
Answer:
Livewire hydrates state from JSON, executes actions, re-renders, and dehydrates state. Queries in mount()
run once on initial load; in render()
, they repeat on every AJAX update, increasing latency.
37. What is the purpose of Gate::after() returning null vs true/false?
Answer:
null
preserves the policy’s decision; true
grants access despite a denial; false
denies access. Use for overrides like super-admin checks.
38. Demonstrate a PHPUnit data provider that tests a policy for multiple roles without duplicating code.
Answer:
public static function roleProvider(): array {
return [['admin', true], ['editor', true], ['guest', false]];
}
/** @dataProvider roleProvider */
public function test_view_policy($role, $expected) {
$user = User::factory()->create(['role' => $role]);
$this->assertSame($expected, $user->can('view', Post::class));
}
39. How can you schedule a task to run every five minutes only when the queue latency exceeds 30 seconds?
Answer:
$schedule->command('jobs:balance')
->everyFiveMinutes()
->when(fn () => Horizon::workload()['wait'] > 30);
The when
closure skips execution if latency is low.
40. Configure Monolog to send WARNING+ logs to Slack while keeping ERROR logs in daily files.
Answer:
In config/logging.php
:
'slack_warn' => [
'driver' => 'monolog',
'handler' => SlackWebhookHandler::class,
'with' => ['webhookUrl' => env('SLACK_WEBHOOK')],
'level' => 'warning',
]
Add slack_warn
and daily
to the stack
channel.
41. Design a custom cache driver using DynamoDB for distributed environments.
Answer:
Implement Store
and LockProvider
with AWS SDK (PutItem
, GetItem
). Register: Cache::extend('dynamo', fn ($app, $config) => new DynamoStore($app->make(DynamoClient::class), $config['table']));
. Configure in cache.php
.
42. Explain how to configure fail-over database connections so reads fall back to a replica and writes fail to primary only.
Answer:
In database.php
: 'host' => ['primary', 'replica']
, with read
and write
arrays. PDO retries failed hosts for reads; writes target primary only.
43. Distinguish read/write splitting from sticky connections in Laravel.
Answer:
Read/write splitting sends SELECT
s to replicas, writes to primary. 'sticky' => true
ensures reads after writes use the primary, avoiding replication lag.
44. Show how to conditionally include a field in an API Resource only when the authenticated user is an admin.
Answer:
'internal_notes' => $this->when(auth()->user()?->isAdmin(), $this->notes),
The field is omitted for non-admins.
45. Describe how publishes() in a service provider enables package configuration overrides.
Answer:
$this->publishes([__DIR__.'/../config/mypkg.php' => config_path('mypkg.php')], 'config');
. Users run php artisan vendor:publish --tag=mypkg-config
to override config without modifying vendor files.
46. How do you implement cascading soft deletes (delete a user → delete posts) without hard deletes?
Answer:
static::deleting(function ($u) {
if (!$u->isForceDeleting()) {
$u->posts()->each->delete();
}
});
Check isForceDeleting()
to apply soft deletes to related posts.
47. Map an immutable Address value object to multiple columns using Laravel’s custom casts.
Answer:
In AddressCast
:
class AddressCast implements CastsAttributes {
public function set($model, $key, $value, $attrs) {
return ['street' => $value->street, 'city' => $value->city];
}
}
Model: protected $casts = ['address' => AddressCast::class];
with street
, city
in $fillable
.
48. What is the CQRS pattern, and how can you implement it in Laravel for a high-traffic application?
Answer:
CQRS separates read/write models. Use league/tactician
for commands (e.g., CreateOrderCommand
). Store events with spatie/laravel-event-sourcing
in an events
table. Update denormalized views (e.g., order_summaries
) via listeners. Dispatch: bus()->dispatch(new CreateOrderCommand($data));
. Scales read/write paths independently.
49. Compare model observers and generic model events for audit logging.
Answer:
Observers group event methods per model (e.g., UserObserver::created
), registered via User::observe(UserObserver::class)
. Generic events use Event::listen('eloquent.*', …)
for multiple models but lack type-hinted dependencies. Observers are more maintainable for specific models.
50. How can you implement database sharding in Laravel to scale a multi-tenant application?
Answer:
Use a ShardManager
to select connections: config(['database.connections.tenant' => config("shards.{$tenant->shard_id}")]); DB::reconnect('tenant');
. Models set protected $connection = 'tenant';
. Use consistent hashing for shard allocation to scale tenant loads.