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:
- 1PHP Fundamentals
- 2Object-Oriented PHP
- 3Laravel Core
- 4Eloquent ORM and Database
- 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.
declare(strict_types=1);
function add(int $a, int $b): int {
return $a + $b;
}
add("3", 4); // TypeError — strict mode is active in this fileWhat 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.
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 // falsePHP 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.
$discount = 0.10;
$applyDiscount = function (float $price) use ($discount): float {
return $price * (1 - $discount);
};
echo $applyDiscount(100); // 90.0By default use captures the value at closure creation time. To capture by reference:
$counter = 0;
$increment = function () use (&$counter): void {
$counter++;
};
$increment();
$increment();
echo $counter; // 2Arrow functions (fn) introduced in PHP 7.4 capture outer variables implicitly by value without use:
$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.
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:
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(); // 1When 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.
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:
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.
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:
{
"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
└── DivisionByZeroErrortry {
$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.
// Bad: UserController handles auth, email, and DB
// Good: separate AuthService, MailService, UserRepositoryO — Open/Closed: Open for extension, closed for modification.
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.
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?
// 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):
function createUser(string $name, int $age = 18, bool $active = true): User { /* ... */ }
$user = createUser(age: 25, name: 'Alice');Union types (PHP 8.0):
function formatId(int|string $id): string {
return (string) $id;
}Readonly properties (PHP 8.1):
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 propertyEnums (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.
- 1The web server forwards the request to
public/index.php. - 2Composer's autoloader is loaded, and the application bootstrap begins.
- 3The
Application(kernel) is created and the HTTP kernel is instantiated. - 4Global middleware stack runs (CORS, cookie encryption, session handling, etc.).
- 5The router matches the URI to a route definition.
- 6Route-specific middleware runs.
- 7The controller action is resolved from the container and executed.
- 8The controller returns a
Response. - 9The response passes back through middleware (responses run in reverse order).
- 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`.
// 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:
$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()`.
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?
class Cache extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'cache';
}
}
// These are equivalent:
Cache::get('users');
app('cache')->get('users');For testing:
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.
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.
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;
}
}// routes/api.php
Route::get('/pro-features', ProController::class)->middleware('subscribed:pro');Terminable middleware runs after the response is sent:
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.
// 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.
<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?
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 |
// 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?
// 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:
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?
// 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:
Relation::morphMap([
'post' => Post::class,
'video' => Video::class,
]);24. What are Eloquent scopes?
Local scope:
public function scopePublished(Builder $query): Builder
{
return $query->where('published', true)->whereNotNull('published_at');
}
Post::published()->byAuthor(42)->latest()->get();Global scope — applied automatically:
// Remove when needed
Post::withoutGlobalScope(SoftDeletingScope::class)->get();
Post::withTrashed()->get();25. What are accessors and mutators in Eloquent?
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:
protected $casts = [
'is_admin' => 'boolean',
'settings' => 'array',
'published_at' => 'datetime',
'status' => PostStatus::class,
];26. What are Eloquent observers?
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.
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?
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?
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.
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:
Bus::chain([
new ValidateOrder($order),
new ChargePayment($order),
new FulfillOrder($order),
])->catch(fn(\Throwable $e) => $order->update(['status' => 'failed']))->dispatch();Job batching:
$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.
$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 itemsSenior 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?
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?
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];
}
}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?
// 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:
Gate::define('delete-post', function (User $user, Post $post) {
return $user->id === $post->user_id || $user->isAdmin();
});Policies — grouped authorization for a model:
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>
@endcan37. What is the repository pattern? When does it add value in Laravel?
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?
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:
- 1Stale singletons: Per-request state in a singleton bleeds to the next user.
- 2Global state: Static properties accumulate unboundedly.
- 3Memory leaks: Objects not garbage-collected grow over time.
// 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?
- 1Profile first — Laravel Telescope or Laravel Debugbar
- 2Fix N+1 —
Model::preventLazyLoading() - 3Add missing indexes — run
EXPLAINon slow queries - 4Cache expensive queries —
Cache::remember() - 5Cache framework overhead —
php artisan optimize - 6Use queues for non-immediate tasks
- 7Paginate large datasets — never
->all()on unbounded tables - 8Select only needed columns —
User::select('id', 'name', 'email') - 9Use
chunkById()for batch processing - 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.
'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.
$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?
// 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?
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
# 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