Laravel Concurrency &amp; Job Batching for Parallel Work | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Job Batching with Laravel Concurrency: Parallel Work Without the Chaos

 Job Batching with Laravel Concurrency: Parallel Work Without the Chaos
=======================================================================

 Learn how to combine Laravel's Concurrency facade with job batching to run parallel workloads safely, avoid shared-state pitfalls, and keep your async pipelines predictable in production.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 17 Aug 2026 · Updated 17 Aug 2026 · 4 min read

ShareCopy linkCopied

 ![Job Batching with Laravel Concurrency: Parallel Work Without the Chaos](https://cdn.msaied.com/559/d55e86a2ecbe32de5e2196f5be63511d.png) 

  On this page +1. [The Problem: Sequential Work Masquerading as Async](#the-problem-sequential-work-masquerading-as-async)
2. [The Concurrency Facade in One Minute](#the-concurrency-facade-in-one-minute)
3. [Choosing the Right Driver](#choosing-the-right-driver)
4. [Combining Concurrency with Job Batching](#combining-concurrency-with-job-batching)
5. [Controlling Concurrency on the Batch Itself](#controlling-concurrency-on-the-batch-itself)
6. [Pitfalls to Avoid](#pitfalls-to-avoid)
7. [1. Passing Eloquent Models into Concurrency Closures](#1-passing-eloquent-models-into-concurrency-closures)
8. [2. Assuming Shared Cache State](#2-assuming-shared-cache-state)
9. [3. Ignoring the sync Driver in Tests](#3-ignoring-the-codesynccode-driver-in-tests)
10. [Takeaways](#takeaways)

 The Problem: Sequential Work Masquerading as Async
--------------------------------------------------

Most Laravel applications that need to "do several things at once" end up dispatching jobs and hoping for the best. That works until you need the *results* of those jobs before moving on — think generating a multi-section report, enriching a batch of records from three external APIs, or running independent validation pipelines in parallel.

Laravel 11 shipped the `Concurrency` facade to solve exactly this. Combined with job batching, you can express parallel work clearly and handle failures without writing a custom orchestration layer.

---

The Concurrency Facade in One Minute
------------------------------------

`Concurrency::run()` accepts an array of closures and executes them in separate PHP processes (via the `fork` driver on Linux/macOS, or a queue-backed driver elsewhere). Each closure is isolated — no shared memory, no race conditions on your objects.

```php
use Illuminate\Support\Facades\Concurrency;

[$orders, $inventory, $pricing] = Concurrency::run([
    fn () => Order::whereUserId($userId)->get(),
    fn () => Inventory::forUser($userId)->available()->get(),
    fn () => PricingService::currentRates(),
]);

```

The return value is an array of results in the same order as the input. Exceptions bubble up as an `\Illuminate\Process\Exceptions\ProcessFailedException` — catch it or let it propagate.

### Choosing the Right Driver

| Driver | When to use | |---|---| | `fork` | CLI/queue workers on Linux; fastest, zero overhead | | `process` | Same as fork but spawns a full PHP process; safer for memory | | `sync` | Testing; runs closures sequentially, no forking |

Set the driver in `config/concurrency.php` or per-call:

```php
Concurrency::driver('fork')->run([...]);

```

---

Combining Concurrency with Job Batching
---------------------------------------

Concurrency is great for short-lived, CPU-bound or I/O-bound tasks that return values. Job batching is better for long-running, queue-distributed work where you need callbacks on completion or failure.

The sweet spot: use `Concurrency` to *fan out* fast preparatory work, then dispatch a `Bus::batch()` for the heavy lifting.

```php
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Concurrency;

// Step 1: fetch metadata in parallel (fast, returns values)
[$segments, $schema] = Concurrency::run([
    fn () => DataSegmentRepository::forExport($exportId),
    fn () => SchemaRegistry::resolve($exportId),
]);

// Step 2: dispatch a batch for the heavy per-segment work
$batch = Bus::batch(
    $segments->map(fn ($seg) => new ProcessSegmentJob($seg, $schema))
)->then(function (Batch $batch) use ($exportId) {
    Export::find($exportId)->markComplete();
})->catch(function (Batch $batch, Throwable $e) use ($exportId) {
    Export::find($exportId)->markFailed($e->getMessage());
})->allowFailures()
  ->dispatch();

```

### Controlling Concurrency on the Batch Itself

Batches don't limit concurrency by default — your queue workers do. If you need to throttle, attach the `WithoutOverlapping` middleware or a rate-limited middleware to the job:

```php
public function middleware(): array
{
    return [
        new RateLimited('segment-processing'),
    ];
}

public function retryUntil(): DateTime
{
    return now()->addMinutes(10);
}

```

---

Pitfalls to Avoid
-----------------

### 1. Passing Eloquent Models into Concurrency Closures

Closures are serialized before being forked. Eloquent models serialize fine, but their open database connections do not. Always pass IDs and re-query inside the closure:

```php
// Bad
Concurrency::run([fn () => $user->enrichProfile()]);

// Good
$userId = $user->id;
Concurrency::run([fn () => User::find($userId)->enrichProfile()]);

```

### 2. Assuming Shared Cache State

Forked processes inherit the parent's memory snapshot but not live cache writes made after the fork. Treat each closure as a fresh request.

### 3. Ignoring the `sync` Driver in Tests

The `sync` driver runs closures sequentially, which is exactly what you want in Pest tests. Bind it in `TestCase::setUp` or use `Concurrency::fake()`.

```php
Concurrency::fake([
    fn () => collect([/* stubbed segments */]),
    fn () => new SchemaStub(),
]);

```

---

Takeaways
---------

- Use `Concurrency::run()` for fast, value-returning parallel tasks; use `Bus::batch()` for distributed, long-running work.
- Always pass primitive IDs into concurrency closures — never live model instances or open connections.
- The `fork` driver is fastest on Linux workers; fall back to `process` if you hit memory issues.
- Test with `Concurrency::fake()` to keep your Pest suite deterministic.
- Combine batch `->then()` / `->catch()` callbacks with `->allowFailures()` for resilient pipelines that report partial success.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [concurrency](https://www.msaied.com/public/articles?search=concurrency)
- [queues](https://www.msaied.com/public/articles?search=queues)
- [async](https://www.msaied.com/public/articles?search=async)

 Frequently asked questions 
---------------------------

  When should I use the Concurrency facade instead of dispatching jobs to a queue?Use Concurrency::run() when you need the results immediately in the same request or CLI command and the work is short-lived (under a few seconds). Use queued jobs when the work is long-running, needs retries, or the caller does not need to wait for results.

   Does the Concurrency facade work on shared hosting or Windows?The fork driver requires pcntl, which is unavailable on Windows and most shared hosts. Switch to the process driver (spawns a full PHP subprocess) or the sync driver for environments without pcntl support.

   How do I handle exceptions thrown inside a Concurrency::run() closure?If any closure throws, Concurrency::run() re-throws the exception in the parent process after all closures finish. Wrap the call in a try/catch and inspect the exception; partial results from successful closures are not returned when an exception occurs.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://www.msaied.com/public#person)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleLet's Encrypt HTTPS on an IP Address With FrankenPHP](https://www.msaied.com/public/articles/lets-encrypt-https-on-an-ip-address-with-frankenphp) [Next articleHow Two Non-Developers Built laracon.us/photos with Claude, Laravel, and Laravel Cloud](https://www.msaied.com/public/articles/how-two-non-developers-built-laraconusphotos-with-claude-laravel-and-laravel-cloud)  

   On this page
-------------

1. [The Problem: Sequential Work Masquerading as Async](#the-problem-sequential-work-masquerading-as-async)
2. [The Concurrency Facade in One Minute](#the-concurrency-facade-in-one-minute)
3. [Choosing the Right Driver](#choosing-the-right-driver)
4. [Combining Concurrency with Job Batching](#combining-concurrency-with-job-batching)
5. [Controlling Concurrency on the Batch Itself](#controlling-concurrency-on-the-batch-itself)
6. [Pitfalls to Avoid](#pitfalls-to-avoid)
7. [1. Passing Eloquent Models into Concurrency Closures](#1-passing-eloquent-models-into-concurrency-closures)
8. [2. Assuming Shared Cache State](#2-assuming-shared-cache-state)
9. [3. Ignoring the sync Driver in Tests](#3-ignoring-the-codesynccode-driver-in-tests)
10. [Takeaways](#takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
