DEV Community

Cover image for Laravel Queue vs defer(): When to Use Each (Laravel 11, 12 & 13)
K. Polash
K. Polash

Posted on

Laravel Queue vs defer(): When to Use Each (Laravel 11, 12 & 13)

Laravel 11 quietly introduced one of the most practical helpers in years: defer().

It lets you run code after the HTTP response has been sent — no queue worker, no Redis, no Supervisor config. Just:

defer(fn () => Metrics::recordOrder($order));
Enter fullscreen mode Exit fullscreen mode

The user gets the response instantly. The closure runs after. Zero extra infrastructure.

But a lot of developers are either ignoring it or misusing it. So here's the honest breakdown.


The One-Line Rule

  • defer() → "I'm okay if this is lost on a crash"
  • Queue Job → "This must succeed, and retry if it fails"

What defer() actually does

Runs a closure in the same PHP process, after the HTTP response is sent.

use function Illuminate\Support\defer;

Route::post('/orders', function (Request $request) {
    $order = Order::create($request->validated());

    defer(fn () => Metrics::recordOrder($order)); // fires AFTER response

    return response()->json($order, 201); // returned immediately
});
Enter fullscreen mode Exit fullscreen mode

Key behaviours:

  • Skips execution if the response is a 4xx or 5xx (use ->always() to override)
  • Can be named and cancelled: defer(fn () => ..., 'name') + defer()->forget('name')
  • Doesn't survive a server crash — not persisted anywhere

Version support: Laravel 11+ only. Not available in Laravel 10 or earlier.


Quick Comparison Table

Feature Queue Job defer()
Needs queue worker ✅ Yes ❌ No
Needs Redis / DB driver ✅ Yes ❌ No
Persisted to storage ✅ Yes ❌ No
Retries on failure ✅ Yes ❌ No
Delayed execution ✅ Yes ❌ No
Monitoring (Horizon) ✅ Yes ❌ No
Zero setup ❌ No ✅ Yes
Runs after response separate process ✅ same process

Use defer() for:

  • Recording analytics after an order
  • Incrementing page view / "last seen" counters
  • Busting a cache key after a resource is created
  • Non-critical internal Slack notifications
  • Lightweight audit trail writes

Use Queue Jobs for:

  • Transactional emails (must not be lost)
  • Payment webhooks and API calls (must retry)
  • Image/file processing (CPU heavy)
  • Delayed tasks ("send reminder in 24 hours")
  • Anything monitored via Horizon or Telescope

⚠️ The Swoole Gotcha

If you use Swoole or FrankenPHP, always import Laravel's defer() explicitly:

// WRONG on Swoole — calls Swoole's defer(), not Laravel's
defer(fn () => Metrics::record($data));

// CORRECT
use function Illuminate\Support\defer;
defer(fn () => Metrics::record($data));
Enter fullscreen mode Exit fullscreen mode

Swoole has its own global defer(). The conflict is silent and will cost you hours.


Testing Deferred Functions

public function test_order_records_metrics(): void
{
    $this->withoutDefer(); // runs deferred closures immediately

    Metrics::shouldReceive('recordOrder')->once();

    $this->postJson('/api/orders', ['product_id' => 1, 'qty' => 2])
         ->assertStatus(201);
}
Enter fullscreen mode Exit fullscreen mode

Full post

For the full decision guide (including the 8-question decision matrix, Laravel 13 notes, and full code examples):

👉 pola5h.github.io/blog/laravel-queue-vs-defer/


What do you use defer() for in your projects? Drop it in the comments 👇

Top comments (0)