go
60 lines · 8 steps
How a debouncer coalesces bursts in Go
A mutex-guarded timer collapses rapid triggers into a single delayed call, running only the latest work.
Explained by
highlit
1package editor
2
3import (
4 "context"
5 "sync"
6 "time"
7)
8
9type Debouncer struct {
10 mu sync.Mutex
11 delay time.Duration
12 timer *time.Timer
13 pending func(context.Context) error
14 onError func(error)
15}
16
17func NewDebouncer(delay time.Duration, onError func(error)) *Debouncer {
18 return &Debouncer{delay: delay, onError: onError}
19}
20
21func (d *Debouncer) Trigger(fn func(context.Context) error) {
22 d.mu.Lock()
23 defer d.mu.Unlock()
24
25 d.pending = fn
26
27 if d.timer != nil {
28 d.timer.Stop()
29 }
30 d.timer = time.AfterFunc(d.delay, d.fire)
31}
32
33func (d *Debouncer) fire() {
34 d.mu.Lock()
35 fn := d.pending
36 d.pending = nil
37 d.timer = nil
38 d.mu.Unlock()
39
40 if fn == nil {
41 return
42 }
43
44 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
45 defer cancel()
46
47 if err := fn(ctx); err != nil && d.onError != nil {
48 d.onError(err)
49 }
50}
51
52func (d *Debouncer) Flush() {
53 d.mu.Lock()
54 if d.timer != nil {
55 d.timer.Stop()
56 d.timer = nil
57 }
58 d.mu.Unlock()
59 d.fire()
60}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Debouncing keeps only the most recent request and resets the clock on every new trigger.
- 2A mutex around shared timer and pending state makes the debouncer safe to call from concurrent goroutines.
- 3Capturing then nil-ing shared state before doing work lets the callback run outside the lock.
Related explainers
go
package handlers type ListFilters struct { Status string `form:"status" binding:"omitempty,oneof=active archived all"`
Cross-field query validation in Gin
validation
struct-tags
query-binding
Intermediate
9 steps
java
package com.example.lb; import java.util.List; import java.util.concurrent.atomic.AtomicInteger;
A thread-safe round-robin load balancer in Java
concurrency
load-balancing
round-robin
Intermediate
9 steps
go
package humanize import ( "fmt"
Parsing human-readable byte sizes in Go
parsing
regex
lookup-table
Intermediate
8 steps
rust
use axum::{extract::{Path, State}, http::StatusCode, Json}; use dashmap::DashMap; use serde::Serialize; use std::sync::Arc;
Request coalescing in an Axum handler
caching
concurrency
request-coalescing
Advanced
8 steps
go
package handlers import ( "net/http"
Serving embedded static meta files in Gin
embedding
static assets
http caching
Intermediate
6 steps
go
package config import ( "fmt"
A thread-safe config singleton in Go
singleton
concurrency
environment-variables
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/how-a-debouncer-coalesces-bursts-in-go-explained-go-613b/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.