Interview Q&A · 62 questions · 25 answered publicly
Laravel Interview Questions
Ordered by how often they're asked. Sign in free to see every answer, rate your confidence and run timed mock interviews.
-
Most asked What is the service container, and how does dependency injection work in Laravel?
Summary
The service container is Laravel's dependency-injection container: it knows how to build objects and their dependencies. When a controller, job or listener type-hints a class or interface, the container resolves it automatically.
Longer Answer
For concrete classes with resolvable constructor arguments, the container uses reflection to build them with zero configuration ("auto-wiring"). For interfaces, or classes that need configuration, you register a binding in a service provider:
bindcreates a new instance every time it is resolved.singletoncreates it once per application lifetime (per request in PHP-FPM, per worker under Octane).scopedcreates it once per request or job, which matters for long-lived workers.- Contextual binding (
when(...)->needs(...)->give(...)) chooses an implementation per consumer.
Injection works in constructors and in controller methods, route closures, job
handlemethods, and anywhere Laravel calls your code through the container. Depending on interfaces and binding implementations in one place is what makes code testable: tests swap the binding for a fake.Code Example
// AppServiceProvider::register() $this->app->singleton(PaymentGateway::class, fn ($app) => new StripeGateway(config('services.stripe.secret'))); // Anywhere the container builds the class, the dependency is injected. class CheckoutController { public function __construct(private PaymentGateway $payments) {} }Key Points
- Auto-wiring via reflection for concrete classes.
bindvssingletonvsscoped.- Bind interfaces to implementations in a service provider.
- Swap bindings in tests.
-
Most asked Which Artisan commands would you run when deploying Laravel to production, and why?
Summary
Cache the configuration, routes, events and views (
php artisan optimize), run migrations with--force, and restart queue workers withqueue:restartso they load the new code.Longer Answer
config:cachemerges all config files into one cached file. After this,env()returns null outside config files, so only callenv()insideconfig/*.php.route:cache,event:cacheandview:cacheremove per-request route registration, listener discovery and Blade compilation.php artisan optimizeruns these together.migrate --forceruns migrations non-interactively. Migrations must be backward compatible with the code still serving traffic during a rolling deploy.queue:restarttells workers to exit gracefully after their current job; the process supervisor restarts them on the new code. Workers otherwise keep running old code in memory.- Composer install with
--no-dev --optimize-autoloader.
With zero-downtime deploys (a new release directory and an atomic symlink switch), also reload PHP-FPM or OPcache so the old cached bytecode is not served.
Key Points
php artisan optimize(config, routes, events, views).- Never call
env()outside config once config is cached. queue:restartafter every deploy.- Migrations must be compatible with the old code.
-
Most asked What is the N+1 query problem in Eloquent and how do you prevent it?
Summary
N+1 happens when you load a list of models (1 query) and then access a relationship on each one, which lazy-loads it with one more query per model (N queries). Fix it with eager loading:
with(),load()orwithCount().Longer Answer
Post::all()followed by$post->author->namein a loop runs one query for posts and one per post for authors. With 500 posts that is 501 queries; locally it feels fine, in production it is slow.Post::with('author', 'comments.user')->get()loads relationships in one query each (usingWHERE IN).$posts->load('author')eager-loads after the fact;loadMissingonly loads what is not loaded yet.withCount('comments'),withSum,withExistsadd aggregates without loading the related rows.- Constrain eager loads:
with(['comments' => fn ($q) => $q->latest()->limit(5)]).
To catch it early, call
Model::preventLazyLoading(! app()->isProduction())in a service provider, so lazy loading throws in development and tests. Laravel Debugbar or Telescope show query counts per request.Code Example
// N+1: one query per order for items, and one per item for its product $orders = Order::where('customer_id', $id)->get(); // Eager loaded: 3 queries in total $orders = Order::with('items.product')->where('customer_id', $id)->get(); // AppServiceProvider::boot() Model::preventLazyLoading(! $this->app->isProduction());Key Points
- One query for the list plus one per row.
with,load,loadMissing,withCount.Model::preventLazyLoading()in non-production.- Measure query counts (Debugbar, Telescope).
-
Most asked What is mass assignment, and how do $fillable and $guarded protect against it?
Summary
Mass assignment is creating or updating a model from an array, such as
User::create($request->all()). Without protection, a client can set columns it should never control, likeis_adminorbalance.Longer Answer
$fillableis an allow-list of attributes that may be mass assigned;$guardedis a deny-list ($guarded = []disables protection entirely). Laravel silently drops attributes that are not fillable (or throws if you enableModel::preventSilentlyDiscardingAttributes()).Model-level protection is the second line of defence. The first is to never pass raw input: validate with a Form Request and pass
$request->validated()(or$request->safe()->only([...])), so only fields you validated reach the model.Prefer
$fillableover$guarded: a new sensitive column added later is protected by default.Code Example
class Order extends Model { protected $fillable = ['shipping_address', 'notes']; } public function update(UpdateOrderRequest $request, Order $order) { $this->authorize('update', $order); $order->update($request->validated()); }Key Points
- Mass assignment = filling a model from an array.
$fillable(allow-list) is safer than$guarded.- Pass
validated()data, never$request->all().
-
Most asked How do database transactions work in Laravel, and what pitfalls should you know?
Summary
DB::transaction(fn () => ...)commits if the closure returns and rolls back if it throws. It accepts a retry count for deadlocks. Side effects such as dispatching jobs or sending mail should wait until after the commit.Longer Answer
DB::transaction($callback, attempts: 3)retries the whole closure when a deadlock occurs. Manual control is available withbeginTransaction,commitandrollBack. Nested calls use savepoints.Pitfalls:
- Jobs and events dispatched inside a transaction may run before the commit and not find the data (or find it rolled back). Use
->afterCommit()on the job or listener, or setafter_commiton the queue connection;DB::afterCommit(fn () => ...)does the same for arbitrary code. - External calls (HTTP, email) inside a transaction keep locks open and cannot be rolled back. Do them after the commit.
- Transactions do not prevent race conditions on their own: for read-then-write logic use
lockForUpdate()(orsharedLock()) inside the transaction, or an atomic conditional update. - Exceptions caught inside the closure do not roll back.
Code Example
DB::transaction(function () use ($orderId) { $order = Order::whereKey($orderId)->lockForUpdate()->firstOrFail(); $order->markPaid(); ShipOrder::dispatch($order)->afterCommit(); }, attempts: 3);Key Points
- Closure form commits or rolls back automatically; retries on deadlock.
- Dispatch jobs
afterCommit. - No external I/O inside transactions.
- Use
lockForUpdate()for read-modify-write.
Deep dive: Transactions, Locking & Concurrency in Code
- Jobs and events dispatched inside a transaction may run before the commit and not find the data (or find it rolled back). Use
-
Most asked How do you validate requests in Laravel? Why use Form Requests?
Summary
Use
$request->validate([...])for simple cases, or a Form Request class that holds the rules, authorization check and custom messages. On failure Laravel redirects back with errors (web) or returns a 422 JSON response (API).Longer Answer
A Form Request (
php artisan make:request StoreOrderRequest) keeps controllers thin and makes validation reusable and testable. It hasrules(),authorize(),messages(),prepareForValidation()for normalising input andafter()hooks for cross-field checks.Useful rules:
required,sometimes,nullable,exists:table,column,unique:table,column(with->ignore($id)on update),Rule::in,Rule::enum, array rules withitems.*.qty,FileandPasswordrule objects, and custom rule classes.After validation, use
$request->validated()or$request->safe()so only validated fields are used. Validation does not replace database constraints: auniquerule can still race, so keep the unique index.Code Example
class StoreOrderRequest extends FormRequest { public function authorize(): bool { return $this->user()->can('create', Order::class); } public function rules(): array { return [ 'items' => ['required', 'array', 'min:1'], 'items.*.product_id' => ['required', 'integer', 'exists:products,id'], 'items.*.qty' => ['required', 'integer', 'between:1,100'], ]; } }Key Points
validate()inline or Form Request classes.- 422 JSON for APIs, redirect with errors for web.
- Use
validated()/safe()data. - Keep database constraints; the
uniquerule can race.
-
Most asked Gates or policies: how does authorization work in Laravel?
Summary
Gates are closures for actions not tied to a model (
viewAdminDashboard); policies are classes that group authorization for one model (OrderPolicy::update($user, $order)). Both are checked withcan,authorize,@canor thecanmiddleware.Longer Answer
Policies are auto-discovered by naming convention (
App\Policies\OrderPolicyforApp\Models\Order). Methods map to actions:viewAny,view,create,update,delete,restore,forceDelete. In a controller,$this->authorize('update', $order)orGate::authorize(...)throws a 403 when denied.before()on a policy (orGate::before) can grant super-admins everything; returnnullto fall through. Responses can carry a message (Response::deny('...')) or hide existence withdenyAsNotFound().The common failure is forgetting the check on one endpoint, especially APIs with route-model binding. Authorize in every controller action (or via
authorizeResource/middleware), and write tests that call endpoints as another user.Key Points
- Gates: closures; policies: per-model classes.
authorize()throws 403.before()for super-admin bypass.- Test that other users get 403/404.
-
Most asked How do queues work in Laravel?
Summary
A job class implementing
ShouldQueueis serialised and pushed to a queue backend (Redis, database, SQS, Beanstalkd). Worker processes (php artisan queue:work) pop jobs and run theirhandlemethod, retrying on failure and moving jobs that keep failing to thefailed_jobstable.Longer Answer
Dispatching (
ProcessPodcast::dispatch($podcast)) stores the job payload. Eloquent models are serialised as their IDs (SerializesModels) and re-fetched when the job runs, so the job sees current data (or fails if the row is gone).Workers are long-running PHP processes managed by Supervisor or systemd (or Horizon for Redis). Key options: queue names and priorities (
--queue=high,default),--tries,--backoff,--timeout,--max-jobs/--max-timeto recycle workers, and--memory.Because workers are long-lived, they keep code and static state in memory: restart them on deploy (
queue:restart) and avoid state leaking between jobs. Thesyncdriver runs jobs inline, useful in tests and local development.Key Points
- Jobs are serialised; models by ID.
- Long-running workers under a supervisor.
- Tries, backoff, timeout, failed_jobs.
- Restart workers on deploy.
Deep dive: Queues & Background Processing in Depth
-
Most asked A Laravel endpoint is slow. How do you find and fix the cause?
Summary
Measure first: look at query count and time (Telescope, Debugbar, slow query log, APM), then fix the biggest cost, usually N+1 queries, missing indexes, too much data loaded, or slow external calls in the request.
Longer Answer
Typical findings, roughly in order of frequency:
- N+1 queries: eager load; enable
preventLazyLoadingin development. - Missing indexes: run
EXPLAINon the slow queries; add composite indexes matchingWHEREandORDER BY. - Loading too much:
get()on large tables,SELECT *, hydrating thousands of models; paginate and select columns. - Synchronous side effects: email, PDF generation, third-party APIs; queue them.
- Repeated expensive computation: cache with a TTL.
- Framework overhead:
php artisan optimize, OPcache, Composer optimized autoloader; Octane if bootstrap cost dominates.
Verify each fix with the same measurement, and add a regression guard (query-count assertions in tests, APM alerts).
Key Points
- Measure before changing anything.
- N+1, indexes, data volume, synchronous I/O.
- Cache and queue where appropriate.
- Confirm with the same metric.
- N+1 queries: eager load; enable
-
Most asked What are the most common security mistakes in Laravel applications?
Summary
Mass assignment from raw input, missing authorization on endpoints, SQL injection through raw queries with interpolated values, XSS through
{!! !!}, exposed debug mode or.env, and secrets committed to the repository.Longer Answer
- Authorization: route-model binding finds any record; every action needs a policy check.
- Mass assignment: use
validated()data and$fillable. - SQL injection: Eloquent and the builder bind values, but
whereRaw("name = '$name'"),DB::statementwith interpolation and user-controlled column names (orderBy($request->sort)) are injectable. Bind values and allow-list column names. - XSS:
{{ }}escapes;{!! !!}does not. Only output trusted or sanitised HTML. - Configuration:
APP_DEBUG=truein production leaks stack traces and environment; the web root must bepublic/so.envis never served. - Uploads: validate types, store outside the web root or on private disks.
- Keep Laravel and packages updated (
composer audit).
Key Points
- Authorize every action.
- Bind values; allow-list sortable columns.
{!! !!}only for trusted HTML.APP_DEBUG=false, web rootpublic/,composer audit.
-
Most asked Walk me through the lifecycle of an HTTP request in Laravel.
Summary
The request enters
public/index.php, the application is bootstrapped, the HTTP kernel runs it through global middleware, the router matches a route and runs route middleware, the controller returns a response, and the response travels back out through the middleware before being sent.Longer Answer
public/index.phploads Composer's autoloader and creates the application frombootstrap/app.php. The HTTP kernel bootstraps the framework: it loads configuration and environment, registers error handling, and registers and then boots every service provider, which is where most of the framework (database, queue, routing, validation) is wired into the container.The request is then sent through the global middleware stack (trimming strings, maintenance mode, trusted proxies). The router matches a route, applies its middleware groups (
webadds sessions, cookies and CSRF;apiis stateless), resolves route-model bindings, and calls the controller or closure with its dependencies injected from the container.Whatever the controller returns (a view, an array, a model, a Response) is converted into a Response object and passes back out through the same middleware in reverse order, which is how middleware can modify responses. Finally the response is sent and terminable middleware runs.
Key Points
- Entry point
public/index.php, app built inbootstrap/app.php. - Service providers register, then boot.
- Global middleware, route matching, route middleware, controller.
- The response goes back out through middleware in reverse order.
Deep dive: Request Lifecycle course: The Laravel Request Lifecycle
- Entry point
-
Most asked How do facades work, and when would you prefer dependency injection instead?
Summary
A facade is a class with static-looking methods that forwards each call to an object resolved from the service container.
Cache::get()is reallyapp('cache')->get().Longer Answer
Each facade defines
getFacadeAccessor(), the container key of the underlying service. The baseFacadeclass implements__callStatic, resolves that service (and caches the instance) and forwards the call. So facades are not static classes: they are a short syntax for container lookups, and they can be faked in tests (Cache::shouldReceive(...),Mail::fake()).Prefer constructor injection when a class depends on many services (the constructor documents the dependencies and shows when a class is doing too much), for code shared outside Laravel, and for domain classes that should not know about the framework. Facades are fine, and idiomatic, in controllers, jobs and glue code.
Real-time facades (
use Facades\App\Services\Pricing;) turn any class into a facade, which is convenient but hides dependencies.Key Points
- Facades proxy to container services via
__callStatic. - They are mockable (
fake(),shouldReceive). - Injection makes dependencies explicit; prefer it in domain code.
- Facades proxy to container services via
-
Most asked How does middleware work in Laravel, and where is it registered in recent versions?
Summary
Middleware wraps the request: each one receives the request and a
$nextclosure, can act before and after calling$next, or return a response early. In Laravel 11 and later it is configured inbootstrap/app.phpwith->withMiddleware(...)instead of an HTTP Kernel class.Longer Answer
Middleware forms a pipeline. Code before
$next($request)runs on the way in (authentication, rate limiting, setting the locale); code after it runs on the way out (adding headers, logging timings). Returning without calling$nextshort-circuits the request, for example a redirect to the login page.Middleware can be global (every request), grouped (
web,api) or assigned to routes by alias, and can take parameters (role:admin). Terminable middleware implementsterminate()and runs after the response has been sent.In Laravel 11's streamlined skeleton,
app/Http/Kernel.phpwas removed; you append, prepend or alias middleware inbootstrap/app.php. Older applications upgraded in place can keep their Kernel.Code Example
class EnsureUserIsAdmin { public function handle(Request $request, Closure $next): Response { if (! $request->user()?->is_admin) { abort(403); } return $next($request); } } // bootstrap/app.php ->withMiddleware(function (Middleware $middleware) { $middleware->alias(['admin' => EnsureUserIsAdmin::class]); })Key Points
- Pipeline of closures: before and after
$next. - Global, group and route middleware; parameters.
- Laravel 11+: configured in
bootstrap/app.php.
- Pipeline of closures: before and after
-
Most asked Eloquent or the query builder: when do you use which?
Summary
Eloquent is the ActiveRecord ORM: models, relationships, events, casts and accessors. The query builder builds SQL fluently and returns plain objects. Both use PDO with bound parameters.
Longer Answer
Eloquent is the default for domain data: relationships, mass-assignment protection, casts, model events, scopes and API resources all build on it. It costs memory and CPU per row because every row becomes a full model object.
The query builder (
DB::table('orders')) is lighter and better for reporting queries, bulk updates and inserts, large exports, and complex SQL (aggregates, window functions, CTEs throughDB::raworselectRaw). Bulk operations through either the builder orModel::query()->update()skip model events, which is often what you want for performance but surprising if observers do important work.Raw SQL is fine when needed; always pass values as bindings (
DB::select('... where id = ?', [$id])).Key Points
- Eloquent: models, relationships, events; heavier per row.
- Query builder: lighter; reports, bulk work, complex SQL.
- Bulk updates skip model events.
- Always bind values in raw SQL.
-
Most asked Explain Eloquent relationship types, including polymorphic relationships.
Summary
One-to-one (
hasOne/belongsTo), one-to-many (hasMany/belongsTo), many-to-many (belongsToManywith a pivot table), has-one/has-many-through, and polymorphic relationships where a model can belong to more than one other model type.Longer Answer
hasMany/belongsTo: the foreign key lives on the child (posts.user_id).belongsToMany: a pivot table (role_user); extra pivot columns withwithPivot, timestamps withwithTimestamps, a custom pivot model withusing().hasManyThrough: reach distant records through an intermediate model (a country's posts through users).- Polymorphic (
morphTo/morphMany): one table serves several parents usingcommentable_typeandcommentable_id, for example comments on posts and videos. Many-to-many polymorphic usesmorphToMany(tags).
Polymorphic relations cannot have real foreign-key constraints, so the database cannot enforce integrity. Use a morph map (
Relation::enforceMorphMap) so class names are not stored in the database and refactors do not break data.Key Points
- Know where the foreign key lives for each type.
- Pivot tables for many-to-many, with extra columns.
- Polymorphic:
*_type+*_id, no FK constraints. - Use an enforced morph map.
-
Most asked How do you process a very large table in Laravel without running out of memory?
Summary
Do not use
get()orall(). UsechunkById()(batches by primary key),lazyById()(a lazy collection over chunks) orcursor()(one model at a time from a single query), and select only the columns you need.Longer Answer
chunk(1000, fn)usesLIMIT/OFFSET. If you modify rows that affect the query'sWHEREclause while iterating, you skip records; offsets also get slower on deep pages.chunkById(1000, fn)paginates withwhere id > ?, so it is stable while you update rows and fast at any depth. Prefer it.lazy()/lazyById()return aLazyCollectionthat fetches chunks behind the scenes, so you can write a normalforeach.cursor()runs one query and hydrates one model at a time. Memory for models stays low, but the PDO driver may still buffer the entire result set (MySQL buffers by default), and eager loading is not possible.
For bulk updates, prefer one SQL statement (
Order::where(...)->update([...])) over loading models at all. For heavy work, dispatch one queued job per chunk.Code Example
User::query() ->where('newsletter_opt_in', true) ->select(['id', 'email']) ->chunkById(1000, function ($users) { SendNewsletterChunk::dispatch($users->pluck('id')->all()); });Key Points
chunkById/lazyByIdoverchunkwhen updating rows.cursorstreams models but the driver may buffer.- Select only needed columns.
- Bulk SQL updates beat loading models.
Deep dive: Code Clinic: review a Laravel queue job
-
Most asked Sanctum or Passport: which do you choose for API authentication?
Summary
Sanctum for first-party SPAs (cookie sessions with CSRF protection) and simple API tokens for mobile apps or integrations; Passport when you need a full OAuth2 server for third-party clients.
Longer Answer
Sanctum has two modes. For an SPA on the same top-level domain it uses Laravel's normal session cookie with CSRF protection, so no tokens are stored in JavaScript. For mobile apps and machine clients it issues personal access tokens (hashed in the database) with optional abilities (scopes) and expiry.
Passport implements OAuth2 (authorization code with PKCE, client credentials, refresh tokens). Choose it when other companies' applications need delegated access to your users' data, the "Sign in with your platform" case.
Many teams pick Passport when Sanctum would do; OAuth2 adds complexity you only need for third-party clients.
Key Points
- Sanctum: SPA cookie auth + simple tokens.
- Passport: full OAuth2 server.
- SPA cookie mode avoids tokens in localStorage.
-
Most asked Explain $tries, $backoff, $timeout and retry_after for Laravel jobs. How do they interact?
Summary
$triesis how many attempts a job gets,$backoffhow long to wait between attempts,$timeouthow long one attempt may run before the worker kills it, and the connection'sretry_afterhow long the queue waits before assuming a job was lost and handing it out again.Longer Answer
retry_after(inconfig/queue.php) must be longer than the longest job$timeout. If it is shorter, the queue releases a job that is still running, and a second worker runs it concurrently: duplicate emails, double charges.$backoffcan be an array for exponential backoff:[10, 60, 300].retryUntil()sets a deadline instead of a count.$maxExceptionsstops retrying after N unhandled exceptions even if releases are allowed.failed(Throwable $e)runs after the final failure, for alerts or compensation.$this->release(30)puts the job back deliberately (for example when rate-limited) and counts as an attempt.
Since any job can run more than once, jobs must be idempotent.
Key Points
retry_aftermust exceed$timeout.- Backoff arrays give exponential delays.
failed()for final-failure handling.- Every job may run twice: make it idempotent.
Deep dive: Queues & Background Processing in Depth
-
Most asked How do you use caching in Laravel effectively, including atomic locks?
Summary
Cache::remember($key, $ttl, fn () => ...)implements cache-aside; choose Redis or Memcached in production; invalidate on writes; and useCache::lock()atomic locks to protect critical sections or prevent cache stampedes.Longer Answer
- Always set a TTL, and invalidate (
Cache::forget) when the underlying data changes. - Version keys (
products:v2:{$id}) to invalidate groups cheaply. Cache tags (Redis, Memcached) group keys but are not supported by every store. Cache::flexible($key, [$fresh, $stale], fn ...)(Laravel 11+) serves stale data while revalidating in the background, which avoids stampedes on hot keys.Cache::lock('report:'.$id, 60)gives a distributed mutex:get()returns immediately,block(5)waits. Release it infinally, or use the closure form.- The
databaseandfiledrivers do not scale across many servers; thearraydriver is per-request.
Do not cache Eloquent models with relationships blindly: serialised objects can be large and become stale or incompatible after a deploy. Cache plain arrays or IDs.
Code Example
$stats = Cache::flexible('dashboard:stats', [60, 300], fn () => Stats::compute()); Cache::lock('invoice:'.$invoice->id, 30)->block(5, function () use ($invoice) { $invoice->finalize(); });Key Points
rememberwith a TTL; invalidate on write.- Atomic locks for critical sections.
flexible(stale-while-revalidate) against stampedes.- Cache arrays, not heavy models.
Deep dive: Database Caching at Scale
- Always set a TTL, and invalidate (
-
Most asked Why should you not call env() outside configuration files?
Summary
Because after
php artisan config:cachethe.envfile is no longer loaded, soenv()returnsnulleverywhere except inside the cached config. Read environment values intoconfig/*.phpand useconfig('services.stripe.key')in code.Longer Answer
Config caching (done in every production deploy) compiles all config files into one PHP array and skips loading
.envon each request, which is faster. Code that callsenv('STRIPE_KEY')directly works locally and silently getsnullin production.Centralising values in config files also documents which variables the app uses, lets you set defaults, and makes values easy to override in tests with
config([...]).Key Points
config:cachestops.envloading.env()only inconfig/*.php.- Use
config()in application code.
-
Most asked How do you test a Laravel application? Feature tests, unit tests and fakes.
Summary
Feature tests send HTTP requests through the whole framework (
$this->postJson('/api/orders', ...)->assertCreated()); unit tests cover isolated classes. Laravel's fakes (Queue::fake(),Mail::fake(),Http::fake(),Event::fake(),Storage::fake()) replace side effects so tests are fast and deterministic.Longer Answer
- Use
RefreshDatabase(wraps each test in a transaction, or migrates once) with a real database engine where possible; SQLite in memory is fast but behaves differently from MySQL or PostgreSQL (JSON, locking, strictness). - Factories create the exact records a test needs.
actingAs($user)authenticates; assert authorization by calling endpoints as the wrong user and expecting 403/404.Http::fake([...])plusHttp::preventStrayRequests()ensure no test calls a real external API.Queue::fake()thenQueue::assertPushed(SendInvoice::class, fn ($job) => ...).- Time:
$this->travel(5)->minutes()orCarbon::setTestNow().
Most value comes from feature tests of real endpoints; unit-test complex domain logic separately.
Key Points
- Feature tests through HTTP; unit tests for logic.
- Fakes for queue, mail, HTTP, events, storage.
RefreshDatabase; beware SQLite differences.preventStrayRequests().
- Use
-
Most asked What are API Resources and why use them?
Summary
API Resources (
JsonResource) transform models into JSON in one place, controlling exactly which fields are exposed, formatting values, including relationships only when loaded, and wrapping collections with pagination metadata.Longer Answer
Returning models directly (
return $order;) exposes every visible column, including ones added later, and couples the API contract to the database schema. A resource declares the contract:public function toArray(Request $request): array { return [ 'id' => $this->id, 'total' => $this->total_cents / 100, 'items' => OrderItemResource::collection($this->whenLoaded('items')), 'notes' => $this->when($request->user()->isStaff(), $this->notes), ]; }whenLoadedavoids triggering lazy loads (N+1) from inside the resource;when/mergeWheninclude fields conditionally. Resource collections addlinksandmetafor paginated results.Key Points
- Explicit, versionable JSON contract.
whenLoadedavoids N+1 in serialisation.- Conditional fields with
when. - Pagination metadata for collections.
-
Most asked What is Laravel Octane, and what changes when you use it?
Summary
Octane serves the app with a long-running server (FrankenPHP, Swoole or RoadRunner) that boots the framework once and keeps it in memory between requests, which removes bootstrap cost and greatly increases throughput. The catch: state now persists across requests.
Longer Answer
Under PHP-FPM every request starts from scratch. Under Octane the application object, singletons and static properties survive between requests, so:
- A singleton that captured the request, the authenticated user or config at construction time will leak it into later requests. Inject the request where it is used, use
scopedbindings, or resolve lazily. - Static arrays used as caches grow forever: memory leaks.
- Octane resets many first-party services between requests, but not your own state.
- Restart workers on deploy (
octane:reload), and use--max-requeststo recycle workers as a safety net.
Swoole also offers concurrent tasks and an in-memory table cache. Profile first: if the bottleneck is the database, Octane will not help much.
Key Points
- Framework booted once; kept in memory.
- Singletons and statics leak state across requests.
- Use scoped bindings; recycle workers.
- Only helps if bootstrap cost matters.
Deep dive: Request Lifecycle course: Laravel Octane and Long-Running Workers
- A singleton that captured the request, the authenticated user or config at construction time will leak it into later requests. Inject the request where it is used, use
-
Most asked How do you deploy a Laravel application with zero downtime?
Summary
Build each release in a new directory (dependencies installed, assets built, config cached), run backward-compatible migrations, switch a symlink atomically, reload PHP-FPM/OPcache, and restart queue workers. Tools such as Envoyer, Deployer or Forge automate this.
Longer Answer
Steps for a typical server deploy:
- Clone to
releases/<timestamp>; link shared.envandstorage/. composer install --no-dev --optimize-autoloader, build front-end assets.php artisan optimize.php artisan migrate --force(expand-only changes).- Atomically switch the
currentsymlink; reload PHP-FPM so OPcache picks up the new files. php artisan queue:restart(orhorizon:terminate), andoctane:reloadif used.- Health check; keep the previous release for instant rollback.
In containers the same ideas apply: an immutable image, migrations as a separate step, rolling updates with readiness probes, and graceful worker shutdown (SIGTERM handling).
Key Points
- New release directory + atomic symlink switch.
- Migrations compatible with old code.
- Reload FPM/OPcache; restart workers.
- Keep the previous release for rollback.
- Clone to
-
Most asked How do you protect an endpoint against race conditions in Laravel, for example two users buying the last item?
Summary
Make the check and the write atomic: a conditional update (
update ... where stock > 0), a row lock inside a transaction (lockForUpdate()), or a distributed lock (Cache::lock) for logic spanning several resources. Validation alone cannot prevent it.Longer Answer
The naive version (read stock, check it, decrement) lets two requests read stock = 1 and both succeed. Options:
- Atomic conditional update:
Product::whereKey($id)->where('stock', '>', 0)->decrement('stock')returns the number of affected rows; 0 means sold out. Simple and fast. - Pessimistic lock: inside
DB::transaction,Product::lockForUpdate()->find($id), then check and update. Other transactions wait for the lock. - Optimistic locking: a
versioncolumn; updatewhere version = ?and retry on conflict. - Cache locks for cross-resource critical sections, keeping the database constraint as the final guarantee.
Key Points
- Read-check-write races without atomicity.
- Conditional update is simplest.
lockForUpdate()inside a transaction.- Optimistic locking with a version column.
Deep dive: Transactions, Locking & Concurrency in Code
- Atomic conditional update:
- How do you write migrations that are safe to deploy with zero downtime?
- How do you make queued jobs idempotent and prevent duplicates in Laravel?
- How do events, listeners and observers work, and when would you use them?
- What is the difference between the register and boot methods of a service provider?
- What is route-model binding? Explain implicit, explicit and scoped binding.
- How do accessors, mutators and attribute casting work in modern Eloquent?
- What are local and global query scopes?
- How do soft deletes work, and what are their downsides?
- How does Laravel protect against CSRF, and when do you exclude routes?
- What is Laravel Horizon and when do you need it?
- How does task scheduling work in Laravel, and how do you run it safely on several servers?
- How do sessions work in Laravel when you run several web servers?
- How do you call external APIs reliably with Laravel's HTTP client?
- paginate, simplePaginate or cursorPaginate: what is the difference?
- How do you implement rate limiting in Laravel?
- How do you handle file uploads and storage in Laravel?
- How do notifications and mail work, and how do you keep them from slowing down requests?
- How does Blade escaping work, and what are components and slots?
- Where should business logic live in a Laravel app? Service classes, actions or repositories?
- How do you handle and report exceptions in Laravel?
- How would you build a multi-tenant SaaS on Laravel?
- Which Eloquent operations do not fire model events, and why does it matter?
- How do you define indexes and foreign keys in Laravel migrations?
- Why can whereHas be slow, and what are the alternatives?
- What is the difference between hashing and encryption in Laravel, and which do you use when?
- What are Laravel's job batching and job chaining, and when would you use each?
- What are Laravel contracts?
- What are model factories and seeders used for?
- Pest or PHPUnit for Laravel tests?
- How does broadcasting work in Laravel (Reverb, Pusher, Echo)?
- Livewire or Inertia: how do you choose a front-end approach for Laravel?
- What are Laravel collections and lazy collections?
- How do you configure logging in Laravel for production?
- How do you upgrade a Laravel application across major versions?
- What are signed URLs and when would you use them?
- How does Laravel's Pipeline work, and where does the framework use it?
- How does maintenance mode work, and how do you use it safely?