Debounced Queued Listeners in Laravel 13.26 | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Debounced Queued Event Listeners in Laravel 13.26        On this page       1. [  The Problem: Redundant Queue Work ](#the-problem-redundant-queue-work)
2. [  Debouncing a Queued Listener ](#debouncing-a-queued-listener)
3. [  How the Debounce Mechanism Works ](#how-the-debounce-mechanism-works)
4. [  Preventing Starvation with maxWait ](#preventing-starvation-with-codemaxwaitcode)
5. [  Rules and Constraints ](#rules-and-constraints)
6. [  Key Takeaways ](#key-takeaways)

  ![Debounced Queued Event Listeners in Laravel 13.26](https://cdn.msaied.com/577/619bf19cd5810dc6898304bb01868b62.png)

 [  Laravel ](https://msaied.com/articles?category=laravel)  #Laravel   #Queues   #Events   #Laravel 13   #Performance  

 Debounced Queued Event Listeners in Laravel 13.26 
===================================================

     21 Aug 2026      4 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   The Problem: Redundant Queue Work  ](#the-problem-redundant-queue-work)
2. [  02   Debouncing a Queued Listener  ](#debouncing-a-queued-listener)
3. [  03   How the Debounce Mechanism Works  ](#how-the-debounce-mechanism-works)
4. [  04   Preventing Starvation with maxWait  ](#preventing-starvation-with-codemaxwaitcode)
5. [  05   Rules and Constraints  ](#rules-and-constraints)
6. [  06   Key Takeaways  ](#key-takeaways)

 The Problem: Redundant Queue Work
---------------------------------

A product import touches the same record forty times in a minute. `ProductUpdated` fires forty times. The listener that rebuilds the search index runs forty times, each run indexing state the next one immediately overwrites. The queue does exactly what it was told, but 97 percent of the work is waste.

What you actually want is for a burst of identical events to collapse into **one listener execution** at the end of the burst, carrying the latest state.

Laravel 13.6 introduced debounceable queued jobs. **Laravel 13.26** extends the same `#[DebounceFor]` attribute to queued event listeners, contributed by [@stevebauman](https://github.com/stevebauman) in [\#61169](https://github.com/laravel/framework/pull/61169), so event-driven code gets the same behavior without restructuring listeners into manually dispatched jobs.

Debouncing a Queued Listener
----------------------------

Add the `#[DebounceFor]` attribute to any listener that implements `ShouldQueue` and specify a debounce window in seconds:

```php
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\DebounceFor;

#[DebounceFor(30, maxWait: 120)]
class UpdateProductSearchIndex implements ShouldQueue
{
    public function debounceId(ProductUpdated $event): string
    {
        return (string) $event->product->getKey();
    }

    public function handle(ProductUpdated $event): void
    {
        ProductIndexer::index($event->product->fresh());
    }
}

```

Every `ProductUpdated` event for product 42 within a 30-second window now results in **one `handle()` call**, made for the last event of the burst. Events for product 43 debounce independently because `debounceId()` keys the window per product.

Without a `debounceId`, all dispatches share one window — ideal for global listeners like "rebuild the sitemap". The ID can also be a plain `$debounceId` property when it does not depend on the event payload.

How the Debounce Mechanism Works
--------------------------------

Each dispatch queues the listener with a delay equal to the debounce window and records an **owner token** in the cache, keyed by listener class and debounce ID. A newer dispatch overwrites the token. When an older queued copy finally executes, it checks whether it still owns the token — if not, it discards itself silently.

One important caveat: a single event on an otherwise idle resource still waits out the full debounce window before executing. There is no "fire immediately on first event" shortcut.

Preventing Starvation with `maxWait`
------------------------------------

Pure debouncing has a failure mode: a continuous stream of events that never pauses long enough for the window to expire defers the listener indefinitely.

`maxWait` solves this. With `#[DebounceFor(30, maxWait: 120)]`, once dispatches have been pushing the window for 120 seconds, the next dispatch executes **without delay** instead of extending the deferral again. A busy import still gets its writes collapsed — roughly one index run per two minutes — rather than either forty runs or zero.

Rules and Constraints
---------------------

Three things to know before rolling this out:

- **No `ShouldBeUnique` together.** Combining the two attributes throws a `LogicException` at dispatch time. They hold opposite semantics — first-wins vs. last-wins — and the framework refuses to pick silently.
- **Debouncing is scoped to the listener, not the event.** Other listeners on `ProductUpdated` still run for every event. Only the attributed listener collapses.
- **Re-read state in the handler.** The event object that survives the debounce is the last one dispatched, but by execution time even it can be stale. The example above calls `$event->product->fresh()` for exactly this reason. Treat the event as a pointer to a resource, not as a complete payload.

That last habit is what makes debouncing safe: if the listener re-derives its output from the database, collapsing forty runs into one changes the cost, not the result.

Key Takeaways
-------------

- `#[DebounceFor(seconds, maxWait: seconds)]` on a `ShouldQueue` listener collapses event bursts into one execution.
- `debounceId()` scopes the debounce window per resource; omit it for global listeners.
- `maxWait` prevents indefinite deferral under sustained event streams.
- Cannot be combined with `ShouldBeUnique`.
- Always call `->fresh()` or re-query state inside the handler; the surviving event object may be stale.

---

*Source: [Debounced Queued Event Listeners in Laravel — Laravel News](https://laravel-news.com/laravel-debounced-queued-listeners)*

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fdebounced-queued-event-listeners-in-laravel-1326&text=Debounced+Queued+Event+Listeners+in+Laravel+13.26) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fdebounced-queued-event-listeners-in-laravel-1326) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  What does the `#\[DebounceFor\]` attribute do on a Laravel queued event listener?        It collapses a burst of identical events into a single listener execution. Each dispatch queues the listener with a delay equal to the debounce window and records an owner token in the cache. If a newer dispatch arrives before the delay expires, it overwrites the token and the older queued copy discards itself when it runs, leaving only the last dispatch to execute. 

      Q02  How does `maxWait` prevent a debounced listener from never running under a continuous event stream?        Without `maxWait`, a stream of events that never pauses for the full debounce window would defer the listener indefinitely. Setting `maxWait` caps the total deferral time: once dispatches have been pushing the window for that many seconds, the next dispatch executes immediately instead of extending the delay again. 

      Q03  Can `#\[DebounceFor\]` be combined with `ShouldBeUnique` on the same listener?        No. Combining them throws a `LogicException` at dispatch time. `ShouldBeUnique` is first-wins (only the first job in the window runs) while `#[DebounceFor]` is last-wins (only the most recent dispatch runs). The framework treats the combination as a logic error rather than silently picking one behavior. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![Practical RAG in Laravel: pgvector, Embeddings, and Retrieval Pipelines](https://cdn.msaied.com/575/21de38adc44ef949b9bdc13ad6f6166b.png) laravel ai pgvector 

### Practical RAG in Laravel: pgvector, Embeddings, and Retrieval Pipelines

Build a production-ready Retrieval-Augmented Generation pipeline in Laravel using pgvector, OpenAI embeddings,...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 21 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/practical-rag-in-laravel-pgvector-embeddings-and-retrieval-pipelines-3) [ ![Agent Run Observability in Laravel AI SDK 0.11](https://cdn.msaied.com/576/2c65c83715560433872bec3ae0eb2bf6.png) Laravel AI AI SDK Observability 

### Agent Run Observability in Laravel AI SDK 0.11

Laravel AI SDK 0.11 ships a single correlation ID per agent run, lifecycle events with wall timings, hosted to...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 20 Aug 2026     4 min read  

  Read    

 ](https://msaied.com/articles/agent-run-observability-in-laravel-ai-sdk-011) [ ![Statamic Mailables Viewer: Preview Laravel Emails in the Control Panel](https://cdn.msaied.com/574/5ecb785e581163f6a143b14cec070996.png) Statamic Laravel Email 

### Statamic Mailables Viewer: Preview Laravel Emails in the Control Panel

Mailables Viewer is a free Statamic add-on by Jack McDade that auto-discovers Laravel mailables and renders li...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 20 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/statamic-mailables-viewer-preview-laravel-emails-in-the-control-panel) 

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

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
