InterviewHack.ai
Empezar gratis
Blog/PHP and Laravel Interview Questions and How to Answer Them (40+ Questions)

PHP and Laravel Interview Questions and How to Answer Them (40+ Questions)

September 16, 2026

phplaravel

A comprehensive guide to PHP and Laravel interview questions covering 44 questions across PHP fundamentals, OOP, Laravel core, Eloquent ORM, and advanced topics including queues, testing, caching, broadcasting, and architecture patterns. Each question includes a full answer, real code examples, and notes on what separates junior from senior answers.

PHP and Laravel Interview Questions and How to Answer Them (40+ Questions)

Whether you are preparing for a junior role or a senior engineering position, PHP and Laravel interviews test layers of knowledge: raw language mechanics, framework internals, database performance, architecture patterns, and production engineering. This guide covers 44 questions — each with a full answer, real code, and notes on what the interviewer is actually measuring.


How This Guide Is Organized

The questions are grouped into five sections:

  1. 1PHP Fundamentals
  2. 2Object-Oriented PHP
  3. 3Laravel Core
  4. 4Eloquent ORM and Database
  5. 5Advanced Laravel (Architecture, Testing, Queues, Security)

A "Senior signal" note is added wherever the answer that separates a mid-level from a senior candidate is non-obvious.


Section 1 — PHP Fundamentals


1. What are PHP's scalar and compound types? What does `declare(strict_types=1)` do?

PHP's scalar types are int, float, string, and bool. The compound types are array, object, callable, and iterable. The special types are null, void, and never (PHP 8.1+).

By default PHP coerces types silently: passing "3" to a function expecting int works without complaint. When you add declare(strict_types=1) at the top of a file, PHP throws a TypeError instead of coercing. The declaration is per-file, not global.

php
declare(strict_types=1);

function add(int $a, int $b): int {
    return $a + $b;
}

add("3", 4); // TypeError — strict mode is active in this file

What interviewers look for: Knowing that coercion exists by default and that strict_types is scoped to the file where it is declared. A junior answer stops at "PHP is loosely typed." A senior answer explains coercion rules, when to enforce strictness, and the edge cases of strict mode with internal functions.


2. What is the difference between `==` and `===` in PHP?

== is a loose comparison that coerces types before comparing. === is a strict comparison that requires both value and type to match.

php
0 == "foo"    // true  in PHP 7 (changed in PHP 8)
0 == ""       // false in PHP 8, true in PHP 7
0 === "0"     // false — different types
null == false // true
null === false // false

PHP 8 changed the behavior of 0 == "non-numeric-string" to return false, which was a long-standing footgun. Senior candidates mention this PHP 8 change and know when to use strcmp() for explicit string comparison.


3. Explain PHP closures. How do you access outer variables inside a closure?

A closure is an anonymous function that can capture variables from its enclosing scope using the use keyword.

php
$discount = 0.10;

$applyDiscount = function (float $price) use ($discount): float {
    return $price * (1 - $discount);
};

echo $applyDiscount(100); // 90.0

By default use captures the value at closure creation time. To capture by reference:

php
$counter = 0;
$increment = function () use (&$counter): void {
    $counter++;
};
$increment();
$increment();
echo $counter; // 2

Arrow functions (fn) introduced in PHP 7.4 capture outer variables implicitly by value without use:

php
$multiplier = 3;
$triple = fn(int $n): int => $n * $multiplier;

Senior signal: Mention that closures implement Closure and have methods like bind(), bindTo(), and call() for changing $this context. Laravel uses Closure::bind() internally in its IoC container and macro system.


4. What are generators and when should you use them?

Generators are functions that use yield to produce values lazily — one at a time — without loading an entire dataset into memory.

php
function readCsvLines(string $path): Generator {
    $handle = fopen($path, 'r');
    while (($line = fgets($handle)) !== false) {
        yield str_getcsv($line);
    }
    fclose($handle);
}

foreach (readCsvLines('million_rows.csv') as $row) {
    processRow($row);
}

Without a generator, loading a million-row CSV into an array would exhaust memory. The generator holds at most one row at a time.

Generators also support send() for two-way communication and yield from for delegation:

php
function fibonacci(): Generator {
    [$a, $b] = [0, 1];
    while (true) {
        yield $a;
        [$a, $b] = [$b, $a + $b];
    }
}

$fib = fibonacci();
echo $fib->current(); // 0
$fib->next();
echo $fib->current(); // 1

When to use: Large file processing, paginating over API results, implementing infinite sequences, or anywhere memory footprint matters more than random access.


5. What are PHP traits and how do they differ from interfaces and abstract classes?

A trait is a reusable code bundle that can be mixed into classes without using inheritance. PHP's single-inheritance model means you cannot inherit from two classes; traits solve horizontal code reuse.

php
trait Timestamps {
    private \DateTime $createdAt;

    public function setCreatedAt(): void {
        $this->createdAt = new \DateTime();
    }

    public function getCreatedAt(): \DateTime {
        return $this->createdAt;
    }
}

class User {
    use Timestamps;
}

class Post {
    use Timestamps;
}

| Concept | Can contain implementation | Can enforce a contract | Multiple per class |

|---|---|---|---|

| Interface | No | Yes | Yes |

| Abstract class | Yes (partial) | Yes | No (single inheritance) |

| Trait | Yes | No | Yes |

Conflict resolution: When two traits define a method with the same name, you must resolve it explicitly:

php
class MyClass {
    use TraitA, TraitB {
        TraitA::hello insteadof TraitB;
        TraitB::hello as helloFromB;
    }
}

Senior signal: Traits are not types. A class using a trait does not pass an instanceof check for the trait. For contracts, use interfaces; for code reuse, use traits; combine both by having the class implement the interface and use the trait that provides the implementation.


6. Explain PHP namespaces and the PSR-4 autoloading standard.

Namespaces prevent name collisions in large codebases. A class App\Models\User and a class Admin\Models\User can coexist because they live in different namespaces.

php
namespace App\Services;

use App\Models\User;
use Illuminate\Support\Facades\Mail;

class UserNotificationService
{
    public function notify(User $user): void
    {
        Mail::to($user->email)->send(new WelcomeMail($user));
    }
}

PSR-4 maps namespace prefixes to filesystem directories:

json
{
  "autoload": {
    "psr-4": {
      "App\\": "app/"
    }
  }
}

This tells Composer that App\Services\UserNotificationService lives at app/Services/UserNotificationService.php. Run composer dump-autoload after changes.


7. What is the difference between `Exception` and `Error`? What is `Throwable`?

Throwable is the top-level interface introduced in PHP 7. Both Exception (user/runtime errors) and Error (engine-level errors) implement it.

Throwable
├── Exception
│   ├── RuntimeException
│   ├── InvalidArgumentException
│   └── LogicException
└── Error
    ├── TypeError
    ├── ParseError
    ├── ArithmeticError
    └── DivisionByZeroError
php
try {
    $result = intdiv(10, 0);
} catch (\DivisionByZeroError $e) {
    echo "Cannot divide by zero";
} catch (\Exception $e) {
    echo $e->getMessage();
} catch (\Throwable $e) {
    echo "Something unexpected: " . $e->getMessage();
}

Before PHP 7, Error did not exist. fatal error: Call to undefined function was uncatchable. Now you can catch \Error.

Senior signal: Set a global fallback with set_exception_handler() and set_error_handler() for legacy code, and know when to convert PHP notices/warnings to exceptions using a custom error handler.


8. What are the key PSR standards every PHP developer should know?

| PSR | Topic | Summary |

|---|---|---|

| PSR-1 | Basic coding standard | Class names in PascalCase, method names in camelCase |

| PSR-2/12 | Coding style | Indentation, braces, spacing (PSR-12 supersedes PSR-2) |

| PSR-3 | Logging interface | LoggerInterface with 8 severity levels |

| PSR-4 | Autoloading | Namespace-to-directory mapping |

| PSR-7 | HTTP messages | Immutable RequestInterface, ResponseInterface |

| PSR-11 | Container interface | ContainerInterface with get() and has() |

| PSR-14 | Event dispatcher | Standard event dispatching contract |

| PSR-15 | HTTP handlers | MiddlewareInterface and RequestHandlerInterface |

Laravel implements PSR-3 (Monolog), PSR-7 (via symfony/psr-http-message-bridge), PSR-11 (the service container), and PSR-14 (event dispatcher).


Section 2 — Object-Oriented PHP


9. Explain the SOLID principles with PHP examples.

S — Single Responsibility: A class should have one reason to change.

php
// Bad: UserController handles auth, email, and DB
// Good: separate AuthService, MailService, UserRepository

O — Open/Closed: Open for extension, closed for modification.

php
interface DiscountStrategy {
    public function apply(float $price): float;
}

class PercentageDiscount implements DiscountStrategy {
    public function __construct(private float $rate) {}
    public function apply(float $price): float {
        return $price * (1 - $this->rate);
    }
}

L — Liskov Substitution: Subclasses must be usable wherever their parent is used without breaking behavior.

I — Interface Segregation: Prefer small, focused interfaces over fat ones.

D — Dependency Inversion: Depend on abstractions, not concretions.

php
class ReportGenerator {
    public function __construct(private LoggerInterface $logger) {}
}

Senior signal: Be able to identify real violations in production codebases. The most common are SRP (controllers doing too much) and DIP (newing up dependencies inside classes).


10. What is dependency injection and the difference between constructor, setter, and method injection?

php
// Constructor injection — preferred
class OrderService {
    public function __construct(
        private readonly PaymentGateway $gateway,
        private readonly OrderRepository $orders,
    ) {}
}

// Setter injection — optional dependencies
class Notifier {
    private ?LoggerInterface $logger = null;

    public function setLogger(LoggerInterface $logger): void {
        $this->logger = $logger;
    }
}

// Method injection — dependency for one specific method
class ReportController extends Controller {
    public function download(Request $request, PdfGenerator $pdf): Response {
        return $pdf->generate($request->input('report_id'));
    }
}

Laravel's container performs method injection for controller actions automatically by reading type hints via reflection.


11. What are PHP 8 named arguments, union types, and readonly properties?

Named arguments (PHP 8.0):

php
function createUser(string $name, int $age = 18, bool $active = true): User { /* ... */ }
$user = createUser(age: 25, name: 'Alice');

Union types (PHP 8.0):

php
function formatId(int|string $id): string {
    return (string) $id;
}

Readonly properties (PHP 8.1):

php
class Money {
    public function __construct(
        public readonly int $amount,
        public readonly string $currency,
    ) {}
}

$m = new Money(100, 'EUR');
$m->amount = 200; // Error: Cannot modify readonly property

Enums (PHP 8.1), fibers (PHP 8.1), and intersection types (PHP 8.1) are also commonly tested at senior level.


Section 3 — Laravel Core


12. Explain the Laravel request lifecycle.

  1. 1The web server forwards the request to public/index.php.
  2. 2Composer's autoloader is loaded, and the application bootstrap begins.
  3. 3The Application (kernel) is created and the HTTP kernel is instantiated.
  4. 4Global middleware stack runs (CORS, cookie encryption, session handling, etc.).
  5. 5The router matches the URI to a route definition.
  6. 6Route-specific middleware runs.
  7. 7The controller action is resolved from the container and executed.
  8. 8The controller returns a Response.
  9. 9The response passes back through middleware (responses run in reverse order).
  10. 10The response is sent to the client.

Senior signal: Console requests go through Console\Kernel instead. Laravel Octane changes this by keeping the application bootstrapped in memory between requests — which has implications for static state and singletons.


13. What is the service container? Explain `bind`, `singleton`, and `scoped`.

php
// bind — new instance every time
$this->app->bind(LoggerInterface::class, FileLogger::class);

// singleton — same instance for the entire application lifetime
$this->app->singleton(CacheInterface::class, RedisCache::class);

// scoped — same instance within one request/job cycle (resets between requests)
$this->app->scoped(TenantContext::class, fn() => new TenantContext());

Contextual binding:

php
$this->app->when(InvoiceController::class)
          ->needs(StorageInterface::class)
          ->give(S3Storage::class);

Senior signal: scoped is critical for Laravel Octane. Using singleton for tenant-specific data causes tenant data to bleed across requests because the same instance is reused.


14. What is a service provider? Explain `register()` vs `boot()`.

php
class PaymentServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // ONLY bind things into the container here.
        $this->app->singleton(PaymentGateway::class, function ($app) {
            return new StripeGateway(config('services.stripe.secret'));
        });
    }

    public function boot(): void
    {
        // ALL providers have registered by the time boot() runs.
        Payment::observe(PaymentObserver::class);

        $this->publishes([
            __DIR__.'/../config/payment.php' => config_path('payment.php'),
        ]);
    }
}

15. What are facades? How do they work under the hood?

php
class Cache extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return 'cache';
    }
}

// These are equivalent:
Cache::get('users');
app('cache')->get('users');

For testing:

php
Cache::fake();
Cache::shouldReceive('get')->once()->with('users')->andReturn([]);

Senior signal: Facades are proxies, not true static methods. Constructor injection with contracts is more explicit and testable; facades are more convenient but make dependencies implicit.


16. What is the difference between contracts and facades?

Contracts are PHP interfaces defined in Illuminate\Contracts.

php
use Illuminate\Contracts\Cache\Repository as CacheContract;

class ProductService
{
    public function __construct(private CacheContract $cache) {}
}

| | Facades | Contracts |

|---|---|---|

| Syntax | Static proxy | Type-hinted interface |

| Dependency visibility | Implicit | Explicit |

| Testability | Via Facade::fake() | Via mock/stub in constructor |


17. How does middleware work? Create a custom middleware.

php
class EnsureUserHasSubscription
{
    public function handle(Request $request, Closure $next, string $plan = 'basic'): mixed
    {
        if (! $request->user()?->hasSubscription($plan)) {
            return response()->json(['error' => 'Subscription required'], 403);
        }

        $response = $next($request);
        $response->headers->set('X-Subscription-Plan', $plan);
        return $response;
    }
}
php
// routes/api.php
Route::get('/pro-features', ProController::class)->middleware('subscribed:pro');

Terminable middleware runs after the response is sent:

php
public function terminate(Request $request, Response $response): void
{
    app(Analytics::class)->track($request, $response);
}

18. How does Laravel authentication work? Sanctum vs. Passport?

Sanctum — first-party SPAs and mobile apps:

  • Session-based auth for SPAs (same domain, cookie-based)
  • Simple API token auth for mobile/third-party clients

Passport — full OAuth2 server for public API platforms.

php
// Sanctum token
$token = $user->createToken('api-access', ['read:orders'])->plainTextToken;

Rule of thumb: Sanctum for your own apps. Passport only when building a public API that third-party developers consume via OAuth.


19. Explain Blade templating. `{{ }}` vs `{!! !!}`.

{{ $variable }} echoes through htmlspecialchars() — XSS-safe.

{!! $variable !!} echoes raw HTML — only for trusted content.

blade
<p>Hello, {{ $user->name }}</p>
{!! $page->content !!}

@if ($user->isAdmin())
    <a href="/admin">Dashboard</a>
@endif

<x-alert type="error" :message="$errorMessage" />

20. How do you create a custom Artisan command?

php
class SendDailyDigest extends Command
{
    protected $signature = 'digest:send
                            {--dry-run : Preview without sending}
                            {--limit=100 : Max users to process}';

    protected $description = 'Send the daily digest email to active users';

    public function handle(): int
    {
        $users = User::active()->limit((int) $this->option('limit'))->get();

        $this->withProgressBar($users, function (User $user) {
            if (! $this->option('dry-run')) {
                SendDigestJob::dispatch($user);
            }
        });

        $this->info("\nDone — processed {$users->count()} users.");
        return Command::SUCCESS;
    }
}

Section 4 — Eloquent ORM and Database


21. Explain Eloquent relationships.

| Relationship | Method | Use case |

|---|---|---|

| hasOne | hasOne(Profile::class) | User has one Profile |

| belongsTo | belongsTo(User::class) | Profile belongs to User |

| hasMany | hasMany(Post::class) | User has many Posts |

| belongsToMany | belongsToMany(Tag::class) | Post has many Tags |

| hasManyThrough | hasManyThrough(Post::class, User::class) | Country has Posts through Users |

| morphMany | morphMany(Comment::class, 'commentable') | Polymorphic Comments |

| morphToMany | morphToMany(Tag::class, 'taggable') | Polymorphic many-to-many |

php
// Many-to-many with pivot data
public function tags(): BelongsToMany
{
    return $this->belongsToMany(Tag::class)
                ->withPivot('applied_by', 'applied_at')
                ->withTimestamps();
}

22. What is the N+1 query problem and how do you solve it?

php
// BAD: N+1 — 1 query for users + 1 per user for posts
$users = User::all();
foreach ($users as $user) {
    echo $user->posts->count();
}

// GOOD: 2 queries total
$users = User::with('posts')->get();

// Nested eager loading
$users = User::with('posts.comments.author')->get();

// Constrained eager loading
$users = User::with(['posts' => fn($q) => $q->where('published', true)])->get();

Detection:

php
Model::preventLazyLoading(! app()->isProduction());

Senior signal: chunkById() over chunk() for large mutable datasets. chunk() uses OFFSET which can skip or duplicate rows under concurrent writes. chunkById() uses primary key ranges, which is safe.


23. What are polymorphic relationships?

php
// Schema: comments(id, body, commentable_id, commentable_type)

class Comment extends Model
{
    public function commentable(): MorphTo
    {
        return $this->morphTo();
    }
}

class Post extends Model
{
    public function comments(): MorphMany
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

Use a morph map to decouple class names from storage:

php
Relation::morphMap([
    'post'  => Post::class,
    'video' => Video::class,
]);

24. What are Eloquent scopes?

Local scope:

php
public function scopePublished(Builder $query): Builder
{
    return $query->where('published', true)->whereNotNull('published_at');
}

Post::published()->byAuthor(42)->latest()->get();

Global scope — applied automatically:

php
// Remove when needed
Post::withoutGlobalScope(SoftDeletingScope::class)->get();
Post::withTrashed()->get();

25. What are accessors and mutators in Eloquent?

php
use Illuminate\Database\Eloquent\Casts\Attribute;

protected function fullName(): Attribute
{
    return Attribute::make(
        get: fn () => "{$this->first_name} {$this->last_name}",
    );
}

protected function password(): Attribute
{
    return Attribute::make(
        set: fn (string $value) => bcrypt($value),
    );
}

Casts for simpler type conversion:

php
protected $casts = [
    'is_admin'     => 'boolean',
    'settings'     => 'array',
    'published_at' => 'datetime',
    'status'       => PostStatus::class,
];

26. What are Eloquent observers?

php
class UserObserver
{
    public function created(User $user): void
    {
        SendWelcomeEmail::dispatch($user);
    }

    public function updated(User $user): void
    {
        if ($user->wasChanged('email')) {
            $user->emailVerifiedAt = null;
            $user->save();
        }
    }
}

// Register
User::observe(UserObserver::class);
// Laravel 10+ attribute syntax
#[ObservedBy(UserObserver::class)]
class User extends Model {}

27. Explain database migrations.

php
return new class extends Migration
{
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->string('title');
            $table->text('body');
            $table->boolean('published')->default(false);
            $table->timestamp('published_at')->nullable();
            $table->timestamps();
            $table->softDeletes();
            $table->index(['user_id', 'published']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

Senior signal: For zero-downtime deployments, migrations must be backward-compatible. Never drop or rename a column in one step — use add → deploy → backfill → remove old column.


28. What are API Resources?

php
class PostResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id'         => $this->id,
            'title'      => $this->title,
            'author'     => new UserResource($this->whenLoaded('author')),
            'tags'       => TagResource::collection($this->whenLoaded('tags')),
            'created_at' => $this->created_at->toIso8601String(),
        ];
    }
}

return PostResource::collection(Post::with('author', 'tags')->paginate());

whenLoaded() prevents accidental N+1 at the serialization layer.


Section 5 — Advanced Laravel


29. How does the Laravel event system work?

php
class UserRegistered
{
    public function __construct(public readonly User $user) {}
}

class SendWelcomeEmail implements ShouldQueue
{
    public function handle(UserRegistered $event): void
    {
        Mail::to($event->user)->send(new WelcomeMail($event->user));
    }
}

// Dispatch
UserRegistered::dispatch($user);

| Approach | When to use |

|---|---|

| Direct call | Simple, single side effect |

| Observer | Multiple side effects on model lifecycle |

| Event + Listener | Side effects span modules; async via ShouldQueue |


30. Explain Laravel queues end to end.

php
class ProcessInvoice implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;
    public int $timeout = 120;

    public function __construct(private readonly Invoice $invoice) {}

    public function handle(PdfService $pdf, StorageInterface $storage): void
    {
        $path = $pdf->generate($this->invoice);
        $storage->put("invoices/{$this->invoice->id}.pdf", $path);
        $this->invoice->update(['pdf_path' => $path, 'processed_at' => now()]);
    }

    public function failed(\Throwable $e): void
    {
        $this->invoice->update(['status' => 'failed']);
    }
}

ProcessInvoice::dispatch($invoice)->onQueue('invoices');

Job chaining:

php
Bus::chain([
    new ValidateOrder($order),
    new ChargePayment($order),
    new FulfillOrder($order),
])->catch(fn(\Throwable $e) => $order->update(['status' => 'failed']))->dispatch();

Job batching:

php
$batch = Bus::batch($lines->map(fn($l) => new ImportLine($l)))
    ->then(fn(Batch $b) => ImportCompleted::dispatch($b))
    ->allowFailures()
    ->dispatch();

31. What is the difference between `queue:work` and `queue:listen`?

| | queue:work | queue:listen |

|---|---|---|

| Process lifecycle | Single long-running | Forks new process per job |

| Code reloading | Must restart after deploys | Picks up changes automatically |

| Production | Yes (with Supervisor) | No |

After deploying: php artisan queue:restart


32. How does caching work in Laravel? Explain cache tags.

php
$value = Cache::get('key', 'default');
Cache::put('key', $value, now()->addHours(1));
$users = Cache::remember('active-users', 3600, fn () => User::active()->get());

// Cache tags (Redis/Memcached only)
Cache::tags(['users'])->put("user:{$id}", $user, 3600);
Cache::tags(['users'])->flush(); // invalidate all tagged items

Senior signal: Discuss cache stampede prevention (locking, staggered TTLs), and php artisan config:cache / route:cache / view:cache for deployment optimizations.


33. How does rate limiting work in Laravel?

php
RateLimiter::for('api', function (Request $request) {
    return $request->user()
        ? Limit::perMinute(60)->by($request->user()->id)
        : Limit::perMinute(10)->by($request->ip());
});

// Apply to routes
Route::middleware('throttle:api')->group(fn() => /* routes */);

// Manual rate limiting
$executed = RateLimiter::attempt(
    key: 'send-sms:' . $user->id,
    maxAttempts: 5,
    callback: fn() => $this->smsService->send($user->phone, $message),
    decaySeconds: 60,
);

34. How does Laravel broadcasting work?

php
class OrderShipped implements ShouldBroadcast
{
    public function __construct(public readonly Order $order) {}

    public function broadcastOn(): array
    {
        return [new PrivateChannel("orders.{$this->order->user_id}")];
    }

    public function broadcastWith(): array
    {
        return ['order_id' => $this->order->id, 'status' => $this->order->status];
    }
}
javascript
Echo.private(`orders.${userId}`)
    .listen('.order.shipped', (event) => {
        console.log('Order shipped:', event.order_id);
    });

Channel types: Channel (public), PrivateChannel (auth required), PresenceChannel (tracks listeners).


35. How do you test Laravel applications?

php
// Feature test
class CreatePostTest extends TestCase
{
    use RefreshDatabase;

    public function test_authenticated_user_can_create_a_post(): void
    {
        $user = User::factory()->create();

        $response = $this->actingAs($user)->postJson('/api/posts', [
            'title' => 'Hello World',
            'body'  => 'My first post content.',
        ]);

        $response->assertCreated()->assertJsonPath('data.title', 'Hello World');
        $this->assertDatabaseHas('posts', ['title' => 'Hello World', 'user_id' => $user->id]);
    }
}

// Faking side effects
Mail::fake();
Queue::fake();
Event::fake();

Mail::assertQueued(WelcomeMail::class);
Queue::assertPushed(SetupUserProfile::class);
Event::assertDispatched(UserRegistered::class);

36. Explain gates vs. policies.

Gates — closures for arbitrary actions:

php
Gate::define('delete-post', function (User $user, Post $post) {
    return $user->id === $post->user_id || $user->isAdmin();
});

Policies — grouped authorization for a model:

php
class PostPolicy
{
    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }

    public function before(User $user): ?bool
    {
        if ($user->isSuperAdmin()) return true;
        return null;
    }
}

// In controller
$this->authorize('update', $post);

// In Blade
@can('update', $post)
    <a href="{{ route('posts.edit', $post) }}">Edit</a>
@endcan

37. What is the repository pattern? When does it add value in Laravel?

php
interface PostRepository
{
    public function findById(int $id): ?Post;
    public function findPublishedByAuthor(int $authorId): Collection;
    public function save(Post $post): Post;
}

class EloquentPostRepository implements PostRepository
{
    public function findById(int $id): ?Post
    {
        return Post::with('author', 'tags')->find($id);
    }
    // ...
}

$this->app->bind(PostRepository::class, EloquentPostRepository::class);

When it adds value: Complex query logic reused across callers; unit-testing services without a database; real possibility of swapping persistence.

When it does not: Just wrapping Eloquent calls one-to-one — complexity with no benefit.

Senior signal: Many experienced developers prefer Eloquent scopes + model methods directly for most cases. The pattern should solve a real problem, not be added dogmatically.


38. How do you implement the Circuit Breaker pattern?

php
class CircuitBreaker
{
    private const FAILURE_THRESHOLD = 5;
    private const COOLDOWN_SECONDS  = 60;

    public function __construct(private \Illuminate\Cache\Repository $cache) {}

    public function call(string $service, callable $operation): mixed
    {
        if ($this->isOpen($service)) {
            throw new ServiceUnavailableException("Circuit open for {$service}");
        }

        try {
            $result = $operation();
            $this->reset($service);
            return $result;
        } catch (\Throwable $e) {
            $this->recordFailure($service);
            throw $e;
        }
    }

    private function isOpen(string $service): bool
    {
        return $this->cache->get("circuit:{$service}:open", false);
    }

    private function recordFailure(string $service): void
    {
        $failures = $this->cache->increment("circuit:{$service}:failures");
        if ($failures >= self::FAILURE_THRESHOLD) {
            $this->cache->put("circuit:{$service}:open", true, self::COOLDOWN_SECONDS);
        }
    }

    private function reset(string $service): void
    {
        $this->cache->forget("circuit:{$service}:failures");
        $this->cache->forget("circuit:{$service}:open");
    }
}

39. How does Laravel Octane improve performance? What pitfalls does it introduce?

Octane keeps the application bootstrapped in memory between requests, eliminating per-request framework bootstrap overhead.

Pitfalls:

  1. 1Stale singletons: Per-request state in a singleton bleeds to the next user.
  2. 2Global state: Static properties accumulate unboundedly.
  3. 3Memory leaks: Objects not garbage-collected grow over time.
php
// DANGEROUS with Octane
$this->app->singleton(CurrentUser::class, fn() => auth()->user());

// SAFE
$this->app->scoped(CurrentUser::class, fn() => auth()->user());

40. How do you optimize a slow Laravel application systematically?

  1. 1Profile first — Laravel Telescope or Laravel Debugbar
  2. 2Fix N+1 — Model::preventLazyLoading()
  3. 3Add missing indexes — run EXPLAIN on slow queries
  4. 4Cache expensive queries — Cache::remember()
  5. 5Cache framework overhead — php artisan optimize
  6. 6Use queues for non-immediate tasks
  7. 7Paginate large datasets — never ->all() on unbounded tables
  8. 8Select only needed columns — User::select('id', 'name', 'email')
  9. 9Use chunkById() for batch processing
  10. 10Consider Laravel Octane for high-concurrency workloads

41. Explain Laravel Horizon.

Horizon is a dashboard and supervisor for Redis queues with real-time monitoring of throughput, runtime, and failure rates.

php
'environments' => [
    'production' => [
        'supervisor-critical' => [
            'queue'        => ['critical', 'high'],
            'maxProcesses' => 5,
            'tries'        => 3,
        ],
    ],
],

Use Horizon when your application uses Redis and you need visibility into queue performance or auto-scaling workers.


42. What are PHP 8.1 Fibers?

Fibers are lightweight, stackful coroutines that can be suspended and resumed. They are not threads; they do not run in parallel.

php
$fiber = new Fiber(function (): void {
    $value = Fiber::suspend('first');
    echo "Resumed with: {$value}\n";
});

$result = $fiber->start();   // Returns 'first'
$fiber->resume('hello');     // Prints "Resumed with: hello"

In practice, Fibers power async libraries like ReactPHP and Amp, and underpin Octane's concurrent job handling.


43. How do you handle database transactions in Laravel?

php
// Auto rollback on exception
DB::transaction(function () use ($order) {
    $order->save();
    $order->items()->saveMany($this->items);
});

// Manual control
DB::beginTransaction();
try {
    $user = User::create($data);
    DB::commit();
} catch (\Throwable $e) {
    DB::rollBack();
    throw $e;
}

// Retry on deadlock
DB::transaction(fn() => /* ... */, attempts: 3);

Senior signal: Events dispatched inside a transaction may broadcast before it commits. Use DB::afterCommit() or the ShouldQueueAfterCommit interface to defer side effects until the transaction succeeds.


44. How do you write a clean, maintainable Laravel controller?

php
class StorePostController extends Controller
{
    public function __invoke(StorePostRequest $request, CreatePostAction $action): PostResource
    {
        $post = $action->execute(user: $request->user(), data: $request->validated());
        return new PostResource($post);
    }
}

class CreatePostAction
{
    public function __construct(
        private readonly PostRepository $posts,
        private readonly TagSyncService $tags,
    ) {}

    public function execute(User $user, array $data): Post
    {
        $post = $this->posts->create([...$data, 'user_id' => $user->id]);
        $this->tags->sync($post, $data['tags'] ?? []);
        PostCreated::dispatch($post);
        return $post;
    }
}

class StorePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('create', Post::class);
    }

    public function rules(): array
    {
        return [
            'title'     => ['required', 'string', 'max:255'],
            'body'      => ['required', 'string', 'min:50'],
            'tags'      => ['array', 'max:10'],
            'tags.*'    => ['integer', 'exists:tags,id'],
            'published' => ['boolean'],
        ];
    }
}

What Interviewers Are Really Measuring

Junior developers are expected to know the framework API, fix N+1 with with(), write basic tests, and know Sanctum vs. Passport.

Mid-level developers are expected to explain the request lifecycle and service container internals, use scopes and observers, write queued jobs with retry logic, and design a clean controller/action structure.

Senior developers are expected to discuss trade-offs (repository pattern: when it helps vs. overhead), know Octane implications for singletons vs. scoped bindings, handle zero-downtime migrations, implement circuit breakers and custom rate limiting, and have opinions — not just facts.

The pattern that distinguishes a senior answer is always the same: they give you the right answer and they tell you when not to use it.


Quick Reference: Artisan Commands You Must Know

bash
# Application
php artisan serve
php artisan key:generate

# Database
php artisan migrate
php artisan migrate:fresh --seed
php artisan migrate:rollback --step=1
php artisan db:seed --class=UserSeeder

# Code generation
php artisan make:model Post -mfsc
php artisan make:controller PostController --resource
php artisan make:request StorePostRequest
php artisan make:resource PostResource
php artisan make:job ProcessInvoice
php artisan make:event UserRegistered
php artisan make:listener SendWelcomeEmail --event=UserRegistered
php artisan make:policy PostPolicy --model=Post
php artisan make:middleware EnsureSubscription
php artisan make:command SendDailyDigest

# Queue
php artisan queue:work
php artisan queue:restart
php artisan queue:failed
php artisan queue:retry all
php artisan horizon

# Cache
php artisan cache:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan optimize
php artisan optimize:clear

# Testing
php artisan test
php artisan test --parallel
php artisan test --filter CreatePostTest

FAQ

What PHP version features are most commonly tested in 2024-2025 interviews?+

PHP 8.x features dominate: named arguments (8.0), union types (8.0), match expressions (8.0), readonly properties (8.1), enums (8.1), fibers (8.1), intersection types (8.1), the DNF type syntax (8.2), and readonly classes (8.2). Interviewers frequently ask about strict_types, the JIT compiler, and why 0 == 'string' changed behavior in PHP 8.

How do I answer the N+1 question in a way that impresses a senior interviewer?+

Go beyond saying 'use with()'. Mention Model::preventLazyLoading() for enforcement in development, explain chunkById() vs chunk() for large mutable tables, and describe how API Resources with whenLoaded() prevent N+1 at the serialization layer. Mention Laravel Debugbar and Telescope as detection tools.

What is the most important distinction between bind, singleton, and scoped in the service container?+

bind creates a fresh instance every time the binding is resolved. singleton creates one instance for the entire application lifetime. scoped creates one instance per request or job cycle, resetting between them. scoped is critical for Laravel Octane compatibility — using singleton for per-request state (like the authenticated user or tenant context) causes data to bleed across requests in Octane.

When should I use events/listeners vs observers vs direct calls?+

Use direct calls for simple, single side effects with no decoupling needed. Use observers to group multiple side effects on a model's lifecycle events (created, updated, deleted) in one organized class. Use events and listeners when side effects span different modules, when multiple listeners respond to one action, or when listeners should run asynchronously via ShouldQueue. Events are also the bridge to broadcasting for real-time WebSocket updates.

Is the repository pattern recommended for Laravel applications?+

It depends on the context. The honest senior answer is: it adds value when you have complex query logic reused across multiple callers, when unit-testing services without a database matters, or when swapping persistence layers is a real possibility. It does not add value when it just wraps Eloquent calls one-to-one, adding a layer of indirection with no benefit. Many experienced Laravel developers use Eloquent scopes and model methods directly for most cases.

What is the difference between Laravel Sanctum and Passport, and how do I choose?+

Sanctum is lightweight and handles two use cases: cookie-based session authentication for first-party SPAs on the same domain, and simple API token authentication for mobile apps or third-party clients. Passport implements a full OAuth2 server with authorization code flow, refresh tokens, and token scopes at OAuth level. Rule of thumb: Sanctum for your own apps and mobile clients; Passport only when building a public API platform that external third-party developers will consume via OAuth flows.

Artículos relacionados

How to Answer Conflict-With-a-Coworker Interview Questions

Learn how to answer conflict-with-a-coworker interview questions with real examples and proven techniques. Stand out in tech and remote job interviews.

How to Answer 'Why Do You Want to Work Here' in Interviews

Discover expert strategies for answering 'why do you want to work here,' tailored for remote tech roles and dollar opportunities. Real, practical interview tips.

Frontend Developer Interview Questions and How to Answer Them (50+)

Complete SEO article covering 54 frontend developer interview questions with detailed answers, real code snippets across HTML, CSS, JavaScript, React, TypeScript, accessibility, security, build tools, and testing.

Full-Stack Developer Interview Questions: How to Answer Like a Pro (45+)

Comprehensive full-stack developer interview guide with 46 numbered questions covering JavaScript/TypeScript, React, CSS, REST APIs, databases, Node.js, system design, security, testing, DevOps, and advanced architecture topics. Each answer includes working code examples and production-level context.

Preparate para tu entrevista real

Pegá el link de tu vacante: investigamos quién te entrevista y te ensayamos en vivo.

Empezar gratis →

¿Tenés entrevista próxima? Instalá el copiloto en vivo →

InterviewHack.ai

Preparate para la entrevista exacta: quién te entrevista, tu CV a medida y coach real.

Producto

VacantesRevisar CV (ATS) gratis¿Cómo suena tu inglés?¿Te pagan bien?Reporte de sueldos LATAMCursos gratisBlogCV a medidaPráctica habladaEs gratis

Empleos remotos

ReactPythonFull-StackLATAMArgentinaMéxicoVer todas →

Preparate

Práctica habladaFrontendBackendAI EngineerPor empresaVendete con tu CV

Empresa

Buscás talentoAcerca deContactoPrivacidadTérminos

© 2026 InterviewHack.ai · Tu CV es tuyo. Nunca se usa para entrenar nada. · Un producto de IA-PTY