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

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

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

  [ home ](https://www.msaied.com)    [ articles ](https://www.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://www.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%2Fwww.msaied.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%2Fwww.msaied.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://www.msaied.com/articles) 

 [ ![Streaming AI Responses in Laravel: Token Budgets, Structured Output, and Agent Contracts](https://cdn.msaied.com/584/3ffe135721c65ab3f9b40401dc3c41de.png) laravel ai llm 

### Streaming AI Responses in Laravel: Token Budgets, Structured Output, and Agent Contracts

Learn how to stream LLM responses in Laravel, enforce token budgets, and lock structured output to typed PHP c...

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

 23 Aug 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/streaming-ai-responses-in-laravel-token-budgets-structured-output-and-agent-contracts) [ ![Typed PHP 8.3 Enums as Eloquent Casts, Route Parameters, and Validation Rules](https://cdn.msaied.com/583/7301900aac0f2ec5d1347df11ce92188.png) laravel php8.3 enums 

### Typed PHP 8.3 Enums as Eloquent Casts, Route Parameters, and Validation Rules

Go beyond basic enum casting. Learn how to wire PHP 8.3 backed enums into Eloquent, route model binding, and c...

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

 23 Aug 2026     1 min read  

  Read    

 ](https://www.msaied.com/articles/typed-php-83-enums-as-eloquent-casts-route-parameters-and-validation-rules) [ ![Livewire v3 Lazy Components, Islands, and Deferred Loading in Practice](https://cdn.msaied.com/582/564b2d098d2b94b53d4f5319ac77d58b.png) livewire laravel performance 

### Livewire v3 Lazy Components, Islands, and Deferred Loading in Practice

Lazy components, islands, and deferred loading in Livewire v3 let you ship fast initial pages without sacrific...

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

 23 Aug 2026     1 min read  

  Read    

 ](https://www.msaied.com/articles/livewire-v3-lazy-components-islands-and-deferred-loading-in-practice-1) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.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://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [Contact](https://www.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)
