Skip to main content

Documentation Index

Fetch the complete documentation index at: https://kawax.biz/llms.txt

Use this file to discover all available pages before exploring further.

What is concurrency?

When you run multiple independent tasks sequentially—such as fetching data from several external APIs or executing multiple database aggregations—the total time equals the sum of each individual task’s time. Running those tasks concurrently means the total time equals the duration of the single longest task. Laravel’s Concurrency facade provides a simple API for this pattern.
The Concurrency facade is available in Laravel 13. The default process driver works without any additional packages.

How it works

Laravel achieves concurrency by serializing the closures you pass in, dispatching them to a hidden Artisan CLI command, and executing each in its own PHP child process. After each closure finishes, the result is serialized back to the parent process. Three drivers are available:
DriverDescription
processDefault. Spawns child PHP processes. Works in both web requests and CLI.
forkRequires spatie/fork. Forks the current process for better performance. CLI only.
syncExecutes closures sequentially in the current process. Useful for testing.

Running concurrent tasks

Pass an array of closures to Concurrency::run(). The return value is an array of each closure’s result, in the same order:
use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\DB;

[$userCount, $orderCount] = Concurrency::run([
    fn () => DB::table('users')->count(),
    fn () => DB::table('orders')->count(),
]);

Choosing a driver

Use the driver() method to select a specific driver for one call:
$results = Concurrency::driver('fork')->run([
    fn () => fetchFromApiA(),
    fn () => fetchFromApiB(),
]);
To change the default driver for the whole application, publish the concurrency configuration file and update the default option:
php artisan config:publish concurrency

The fork driver

The fork driver is faster than process because it forks the running PHP process instead of booting a fresh one. However, PHP does not support forking during web requests, so this driver only works in Artisan commands and queue workers. Install the required package before using it:
composer require spatie/fork
Do not use the fork driver inside a web request. Use it only inside Artisan commands or queue workers.

Deferring tasks with defer()

Use Concurrency::defer() when you want to run tasks concurrently after the HTTP response has been sent to the user. The closures are not executed when defer() is called—they run in the background after the response is returned.
use App\Services\Metrics;
use Illuminate\Support\Facades\Concurrency;

Concurrency::defer([
    fn () => Metrics::report('users'),
    fn () => Metrics::report('orders'),
]);
defer() is ideal for fire-and-forget work that you do not want to make the user wait for, such as recording analytics or warming a cache.

Practical examples

Calling multiple external APIs at once

Without concurrency, three one-second API calls take three seconds total. With concurrency, they take roughly one second:
use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\Http;

// Sequential — ~3 seconds
public function dashboardSlow(): array
{
    $inventory = Http::get('https://api.example.com/inventory')->json();
    $sales     = Http::get('https://api.example.com/sales')->json();
    $shipping  = Http::get('https://api.example.com/shipping')->json();

    return compact('inventory', 'sales', 'shipping');
}

// Concurrent — ~1 second
public function dashboard(): array
{
    [$inventory, $sales, $shipping] = Concurrency::run([
        fn () => Http::get('https://api.example.com/inventory')->json(),
        fn () => Http::get('https://api.example.com/sales')->json(),
        fn () => Http::get('https://api.example.com/shipping')->json(),
    ]);

    return compact('inventory', 'sales', 'shipping');
}

Running multiple database aggregations

use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\DB;

public function statistics(): array
{
    [$totalUsers, $activeUsers, $totalOrders, $revenue] = Concurrency::run([
        fn () => DB::table('users')->count(),
        fn () => DB::table('users')->where('active', true)->count(),
        fn () => DB::table('orders')->count(),
        fn () => DB::table('orders')->sum('total_amount'),
    ]);

    return compact('totalUsers', 'activeUsers', 'totalOrders', 'revenue');
}
Each child process opened by the process driver establishes its own database connection. Watch your database’s maximum connection limit when running many concurrent tasks.

Testing

Use Concurrency::fake() in tests to switch to the sync driver, which executes closures sequentially without spawning child processes:
use Illuminate\Support\Facades\Concurrency;

public function test_dashboard_returns_correct_data(): void
{
    Concurrency::fake();

    $response = $this->get('/dashboard');

    $response->assertStatus(200);
}
You can also set CONCURRENCY_DRIVER=sync in .env.testing to apply this globally across all tests.

Caveats

Laravel serializes closures to pass them to child processes. You cannot capture objects that are not serializable (database connections, file handles, open resources). Acquire those resources inside the closure instead.
// Bad: capturing an Eloquent model that may not serialize cleanly
$user = User::find(1);
Concurrency::run([
    fn () => $user->orders()->count(),
]);

// Good: fetch data inside the closure
$userId = 1;
Concurrency::run([
    fn () => Order::where('user_id', $userId)->count(),
]);
Spawning a child PHP process takes time. If a task takes only a few milliseconds, the overhead of the process driver may outweigh the benefit. Concurrency shines for tasks that take 100 ms or more, such as HTTP requests or heavy database queries.
If a closure throws an exception, run() re-throws it to the caller. If you want other closures to continue when one fails, wrap the body in a try/catch and return a fallback value.
Concurrency::run([
    function () {
        try {
            return Http::get('https://api.example.com/data')->json();
        } catch (\Exception $e) {
            return null;
        }
    },
]);

Queues

Learn how to defer work to background jobs using Laravel queues.
Last modified on March 29, 2026