> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Concurrency

> Learn how to run multiple operations concurrently using Laravel 13's Concurrency facade to improve application performance.

## What is concurrency

If you execute multiple independent operations—such as requests to several external APIs or database aggregations—**sequentially**, the total time is the sum of each operation.
If you execute them **concurrently**, the total time is reduced to about the time of the single slowest operation.

Laravel's `Concurrency` facade provides a simple API for exactly this kind of concurrent execution.

<Info>
  The `Concurrency` facade was introduced in Laravel 11 and continues to be available in Laravel 13.
  The default driver uses child PHP processes, so it works without any additional packages.
</Info>

## How it works

The `Concurrency` facade serializes the closures you pass in, sends them to a hidden Artisan command, and runs each one as a separate PHP process.
When each operation completes, the return value is serialized back to the parent process.

Three drivers are available.

| Driver    | Description                                                                                    |
| --------- | ---------------------------------------------------------------------------------------------- |
| `process` | The default. Runs by starting child PHP processes. Works even during web requests              |
| `fork`    | Requires the `spatie/fork` package. Forks processes, so it only works in the CLI but is faster |
| `sync`    | Doesn't run concurrently; runs sequentially. Well-suited for testing                           |

## Basic usage

### Concurrency::run()

If you pass an array of closures to the `run()` method, they are executed concurrently.
The return value is an array of each closure's return values.

```php theme={null}
use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\DB;

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

You can destructure the array to assign each result to a variable.
The order of the return values matches the order of the closures.

### Specifying a driver

To use a specific driver, use the `driver()` method.

```php theme={null}
$results = Concurrency::driver('fork')->run([
    fn () => fetchFromApiA(),
    fn () => fetchFromApiB(),
]);
```

To change the default driver, publish the configuration file and update the `default` option.

```shell theme={null}
php artisan config:publish concurrency
```

## Using the fork driver

The `fork` driver is faster than the `process` driver, but it only works in a PHP CLI environment (Artisan commands or queue workers).
It cannot be used during a web request.

Before using it, install the `spatie/fork` package.

```shell theme={null}
composer require spatie/fork
```

<Warning>
  The `fork` driver does not work during web requests. Use it when running concurrency inside Artisan commands or queue workers.
</Warning>

## Not caring about the return value: Concurrency::defer()

If you aren't interested in the results and want the operation to run in the background after the HTTP response has been returned, use the `defer()` method.

```php theme={null}
use App\Services\Metrics;
use Illuminate\Support\Facades\Concurrency;

Concurrency::defer([
    fn () => Metrics::report('users'),
    fn () => Metrics::report('orders'),
]);
```

The closures are not executed at the moment you call `defer()`.
They run concurrently after the HTTP response has been sent to the user.

<Tip>
  `defer()` is a great fit for operations you don't want to make the user wait for, such as recording analytics data or warming up caches.
</Tip>

## Practical example: calling multiple external APIs concurrently

Consider a dashboard for an e-commerce site that fetches information from three APIs: inventory management, sales aggregation, and shipping status.

### Sequential execution (before)

```php theme={null}
use Illuminate\Support\Facades\Http;

public function dashboard(): array
{
    // Wait for each request in turn (about 3 seconds total)
    $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 execution (after)

```php theme={null}
use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\Http;

public function dashboard(): array
{
    // Run three requests at the same time (completes in about the time of the slowest API)
    [$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');
}
```

If each API takes 1 second, sequential execution takes 3 seconds in total, while concurrent execution completes in about 1 second.

### Running multiple database aggregations concurrently

```php theme={null}
use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;

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 [
        'total_users'  => $totalUsers,
        'active_users' => $activeUsers,
        'total_orders' => $totalOrders,
        'revenue'      => $revenue,
    ];
}
```

<Tip>
  When using the `process` driver for database aggregations, each child process opens a new database connection.
  When you run many operations concurrently, watch out for your database's maximum connection limit.
</Tip>

## Configuration for testing

In your test environment, using the `sync` driver runs closures sequentially.
There is no process-startup overhead from concurrency, so tests run faster.

```php theme={null}
// tests/Feature/DashboardTest.php
use Illuminate\Support\Facades\Concurrency;

public function test_dashboard_returns_correct_data(): void
{
    Concurrency::fake(); // Switch to the sync driver

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

    $response->assertStatus(200);
}
```

Alternatively, you can change the default driver in `.env.testing`.

```ini theme={null}
CONCURRENCY_DRIVER=sync
```

## Caveats

<AccordionGroup>
  <Accordion title="Closure limitations">
    Closures are serialized and passed to child processes.
    You cannot capture unserializable objects (such as database connections, file handles, or resources) from the enclosing scope.
    Recreate the objects you need inside the closure.

    ```php theme={null}
    // BAD: capturing an outer Eloquent model
    $user = User::find(1);
    Concurrency::run([
        fn () => $user->orders()->count(), // May not be serializable
    ]);

    // GOOD: fetch the data inside the closure
    $userId = 1;
    Concurrency::run([
        fn () => Order::where('user_id', $userId)->count(),
    ]);
    ```
  </Accordion>

  <Accordion title="process driver overhead">
    The `process` driver has a child-process startup cost, so for very short operations (a few milliseconds or less), concurrent execution may not be faster.
    It shines when parallelizing operations that take at least 100ms, such as HTTP requests or database aggregations.
  </Accordion>

  <Accordion title="fork driver limitations">
    The `fork` driver forks the PHP process, so it cannot be used during web requests (FPM or Apache).
    Only use it inside Artisan commands or queue workers.
  </Accordion>

  <Accordion title="Exception handling">
    If an exception occurs during concurrent execution, `run()` re-throws the exception.
    If you want the other operations to continue when an individual operation raises, use `try/catch` inside the closure.

    ```php theme={null}
    Concurrency::run([
        function () {
            try {
                return Http::get('https://api.example.com/data')->json();
            } catch (\Exception $e) {
                return null; // Return null on failure
            }
        },
    ]);
    ```
  </Accordion>
</AccordionGroup>

## Next steps

<Card title="Queues and jobs" icon="layer-group" href="/en/queues">
  Learn how to run operations asynchronously in the background with queues and jobs.
</Card>


## Related topics

- [Concurrency](/en/packages/laravel-copilot-sdk/concurrency.md)
- [Upgrade guide: Laravel 11 to 12](/en/blog/upgrade-11-to-12.md)
- [HTTP client](/en/http-client.md)
- [April 2026 Laravel updates](/en/blog/changelog/202604.md)
- [Queue Job Execution Control](/en/advanced/queue-job-control.md)
