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.

  1. 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:

    • bind creates a new instance every time it is resolved.
    • singleton creates it once per application lifetime (per request in PHP-FPM, per worker under Octane).
    • scoped creates 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 handle methods, 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.
    • bind vs singleton vs scoped.
    • Bind interfaces to implementations in a service provider.
    • Swap bindings in tests.

    Permalink

  2. 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 with queue:restart so they load the new code.

    Longer Answer

    • config:cache merges all config files into one cached file. After this, env() returns null outside config files, so only call env() inside config/*.php.
    • route:cache, event:cache and view:cache remove per-request route registration, listener discovery and Blade compilation. php artisan optimize runs these together.
    • migrate --force runs migrations non-interactively. Migrations must be backward compatible with the code still serving traffic during a rolling deploy.
    • queue:restart tells 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:restart after every deploy.
    • Migrations must be compatible with the old code.

    Permalink

  3. 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() or withCount().

    Longer Answer

    Post::all() followed by $post->author->name in 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 (using WHERE IN).
    • $posts->load('author') eager-loads after the fact; loadMissing only loads what is not loaded yet.
    • withCount('comments'), withSum, withExists add 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).

    Deep dive: Code Clinic: review a Laravel orders controller

    Permalink

  4. 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, like is_admin or balance.

    Longer Answer

    $fillable is an allow-list of attributes that may be mass assigned; $guarded is a deny-list ($guarded = [] disables protection entirely). Laravel silently drops attributes that are not fillable (or throws if you enable Model::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 $fillable over $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().

    Deep dive: Code Clinic: review a Laravel orders controller

    Permalink

  5. 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 with beginTransaction, commit and rollBack. 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 set after_commit on 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() (or sharedLock()) 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

    Permalink

  6. 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 has rules(), authorize(), messages(), prepareForValidation() for normalising input and after() 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 with items.*.qty, File and Password rule 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: a unique rule 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 unique rule can race.

    Permalink

  7. 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 with can, authorize, @can or the can middleware.

    Longer Answer

    Policies are auto-discovered by naming convention (App\Policies\OrderPolicy for App\Models\Order). Methods map to actions: viewAny, view, create, update, delete, restore, forceDelete. In a controller, $this->authorize('update', $order) or Gate::authorize(...) throws a 403 when denied.

    before() on a policy (or Gate::before) can grant super-admins everything; return null to fall through. Responses can carry a message (Response::deny('...')) or hide existence with denyAsNotFound().

    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.

    Deep dive: Code Clinic: review a Laravel orders controller

    Permalink

  8. Most asked How do queues work in Laravel?

    Summary

    A job class implementing ShouldQueue is serialised and pushed to a queue backend (Redis, database, SQS, Beanstalkd). Worker processes (php artisan queue:work) pop jobs and run their handle method, retrying on failure and moving jobs that keep failing to the failed_jobs table.

    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-time to 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. The sync driver 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

    Permalink

  9. 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:

    1. N+1 queries: eager load; enable preventLazyLoading in development.
    2. Missing indexes: run EXPLAIN on the slow queries; add composite indexes matching WHERE and ORDER BY.
    3. Loading too much: get() on large tables, SELECT *, hydrating thousands of models; paginate and select columns.
    4. Synchronous side effects: email, PDF generation, third-party APIs; queue them.
    5. Repeated expensive computation: cache with a TTL.
    6. 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.

    Deep dive: Code Clinic: review a Laravel orders controller

    Permalink

  10. 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::statement with 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=true in production leaks stack traces and environment; the web root must be public/ so .env is 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 root public/, composer audit.

    Deep dive: Code Clinic: review a Laravel orders controller

    Permalink

  11. 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.php loads Composer's autoloader and creates the application from bootstrap/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 (web adds sessions, cookies and CSRF; api is 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 in bootstrap/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

    Permalink

  12. 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 really app('cache')->get().

    Longer Answer

    Each facade defines getFacadeAccessor(), the container key of the underlying service. The base Facade class 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.

    Permalink

  13. 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 $next closure, can act before and after calling $next, or return a response early. In Laravel 11 and later it is configured in bootstrap/app.php with ->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 $next short-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 implements terminate() and runs after the response has been sent.

    In Laravel 11's streamlined skeleton, app/Http/Kernel.php was removed; you append, prepend or alias middleware in bootstrap/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.

    Permalink

  14. 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 through DB::raw or selectRaw). Bulk operations through either the builder or Model::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.

    Permalink

  15. Most asked Explain Eloquent relationship types, including polymorphic relationships.

    Summary

    One-to-one (hasOne/belongsTo), one-to-many (hasMany/belongsTo), many-to-many (belongsToMany with 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 with withPivot, timestamps with withTimestamps, a custom pivot model with using().
    • hasManyThrough: reach distant records through an intermediate model (a country's posts through users).
    • Polymorphic (morphTo / morphMany): one table serves several parents using commentable_type and commentable_id, for example comments on posts and videos. Many-to-many polymorphic uses morphToMany (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.

    Permalink

  16. Most asked How do you process a very large table in Laravel without running out of memory?

    Summary

    Do not use get() or all(). Use chunkById() (batches by primary key), lazyById() (a lazy collection over chunks) or cursor() (one model at a time from a single query), and select only the columns you need.

    Longer Answer

    • chunk(1000, fn) uses LIMIT/OFFSET. If you modify rows that affect the query's WHERE clause while iterating, you skip records; offsets also get slower on deep pages.
    • chunkById(1000, fn) paginates with where id > ?, so it is stable while you update rows and fast at any depth. Prefer it.
    • lazy() / lazyById() return a LazyCollection that fetches chunks behind the scenes, so you can write a normal foreach.
    • 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 / lazyById over chunk when updating rows.
    • cursor streams 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

    Permalink

  17. 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.

    Permalink

  18. Most asked Explain $tries, $backoff, $timeout and retry_after for Laravel jobs. How do they interact?

    Summary

    $tries is how many attempts a job gets, $backoff how long to wait between attempts, $timeout how long one attempt may run before the worker kills it, and the connection's retry_after how long the queue waits before assuming a job was lost and handing it out again.

    Longer Answer

    retry_after (in config/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.

    • $backoff can be an array for exponential backoff: [10, 60, 300].
    • retryUntil() sets a deadline instead of a count.
    • $maxExceptions stops 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_after must 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

    Permalink

  19. 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 use Cache::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 in finally, or use the closure form.
    • The database and file drivers do not scale across many servers; the array driver 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

    • remember with 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

    Permalink

  20. Most asked Why should you not call env() outside configuration files?

    Summary

    Because after php artisan config:cache the .env file is no longer loaded, so env() returns null everywhere except inside the cached config. Read environment values into config/*.php and use config('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 .env on each request, which is faster. Code that calls env('STRIPE_KEY') directly works locally and silently gets null in 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:cache stops .env loading.
    • env() only in config/*.php.
    • Use config() in application code.

    Permalink

  21. 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([...]) plus Http::preventStrayRequests() ensure no test calls a real external API.
    • Queue::fake() then Queue::assertPushed(SendInvoice::class, fn ($job) => ...).
    • Time: $this->travel(5)->minutes() or Carbon::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().

    Permalink

  22. 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),
        ];
    }
    

    whenLoaded avoids triggering lazy loads (N+1) from inside the resource; when/mergeWhen include fields conditionally. Resource collections add links and meta for paginated results.

    Key Points

    • Explicit, versionable JSON contract.
    • whenLoaded avoids N+1 in serialisation.
    • Conditional fields with when.
    • Pagination metadata for collections.

    Deep dive: Code Clinic: review a Laravel orders controller

    Permalink

  23. 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 scoped bindings, 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-requests to 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

    Permalink

  24. 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:

    1. Clone to releases/<timestamp>; link shared .env and storage/.
    2. composer install --no-dev --optimize-autoloader, build front-end assets.
    3. php artisan optimize.
    4. php artisan migrate --force (expand-only changes).
    5. Atomically switch the current symlink; reload PHP-FPM so OPcache picks up the new files.
    6. php artisan queue:restart (or horizon:terminate), and octane:reload if used.
    7. 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.

    Permalink

  25. 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 version column; update where 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

    Permalink

  26. How do you write migrations that are safe to deploy with zero downtime?
  27. How do you make queued jobs idempotent and prevent duplicates in Laravel?
  28. How do events, listeners and observers work, and when would you use them?
  29. What is the difference between the register and boot methods of a service provider?
  30. What is route-model binding? Explain implicit, explicit and scoped binding.
  31. How do accessors, mutators and attribute casting work in modern Eloquent?
  32. What are local and global query scopes?
  33. How do soft deletes work, and what are their downsides?
  34. How does Laravel protect against CSRF, and when do you exclude routes?
  35. What is Laravel Horizon and when do you need it?
  36. How does task scheduling work in Laravel, and how do you run it safely on several servers?
  37. How do sessions work in Laravel when you run several web servers?
  38. How do you call external APIs reliably with Laravel's HTTP client?
  39. paginate, simplePaginate or cursorPaginate: what is the difference?
  40. How do you implement rate limiting in Laravel?
  41. How do you handle file uploads and storage in Laravel?
  42. How do notifications and mail work, and how do you keep them from slowing down requests?
  43. How does Blade escaping work, and what are components and slots?
  44. Where should business logic live in a Laravel app? Service classes, actions or repositories?
  45. How do you handle and report exceptions in Laravel?
  46. How would you build a multi-tenant SaaS on Laravel?
  47. Which Eloquent operations do not fire model events, and why does it matter?
  48. How do you define indexes and foreign keys in Laravel migrations?
  49. Why can whereHas be slow, and what are the alternatives?
  50. What is the difference between hashing and encryption in Laravel, and which do you use when?
  51. What are Laravel's job batching and job chaining, and when would you use each?
  52. What are Laravel contracts?
  53. What are model factories and seeders used for?
  54. Pest or PHPUnit for Laravel tests?
  55. How does broadcasting work in Laravel (Reverb, Pusher, Echo)?
  56. Livewire or Inertia: how do you choose a front-end approach for Laravel?
  57. What are Laravel collections and lazy collections?
  58. How do you configure logging in Laravel for production?
  59. How do you upgrade a Laravel application across major versions?
  60. What are signed URLs and when would you use them?
  61. How does Laravel's Pipeline work, and where does the framework use it?
  62. How does maintenance mode work, and how do you use it safely?