A simple, thread-safe debounce library for Go that delays function execution until after a specified duration has elapsed since the last invocation. Perfect for rate limiting, reducing redundant operations, and optimizing performance in high-frequency scenarios.
- Thread-safe: Safe for concurrent use across multiple goroutines
- Configurable delays: Set custom debounce durations
- Call limits: Execute immediately after a maximum number of calls
- Time limits: Execute immediately after a maximum wait time
- Function flexibility: Each call can provide a different function to execute
- Zero dependencies: Built using only Go standard library
go get github.com/your-username/debounce
package main
import (
"fmt"
"time"
"github.com/your-username/debounce"
)
func main() {
// Create a debounced function with 500ms delay
debounced := debounce.New(500 * time.Millisecond)
// This will only execute once, after 500ms
debounced(func() {
fmt.Println("Hello, World!")
})
debounced(func() {
fmt.Println("This will be executed instead")
})
// Wait for execution
time.Sleep(1 * time.Second)
}
debounced := debounce.New(200 * time.Millisecond)
// Rapid calls - only the last one executes
for i := 0; i < 10; i++ {
debounced(func() {
fmt.Printf("Executed at %v\n", time.Now())
})
time.Sleep(50 * time.Millisecond) // Less than debounce duration
}
Execute immediately after a specified number of calls:
debounced := debounce.New(
1*time.Second,
debounce.WithMaxCalls(5),
)
// Will execute immediately after 5 calls
for i := 0; i < 10; i++ {
debounced(func() {
fmt.Printf("Executed after %d calls\n", i+1)
})
}
Execute immediately after a maximum wait time, regardless of debounce duration:
debounced := debounce.New(
10*time.Second, // Long debounce duration
debounce.WithMaxWait(2*time.Second), // But execute after 2 seconds max
)
// Will execute after 2 seconds, not 10
debounced(func() {
fmt.Println("Executed due to time limit")
})
debounced := debounce.New(
5*time.Second,
debounce.WithMaxCalls(3),
debounce.WithMaxWait(2*time.Second),
)
// Executes when either:
// - 3 calls are made, OR
// - 2 seconds have passed, OR
// - 5 seconds pass without new calls
package main
import (
"fmt"
"time"
"github.com/your-username/debounce"
)
func searchAPI(query string) {
fmt.Printf("Searching for: %s\n", query)
// Simulate API call
}
func main() {
// Debounce search to avoid excessive API calls
debouncedSearch := debounce.New(300 * time.Millisecond)
// Simulate rapid user typing
queries := []string{"h", "he", "hel", "hell", "hello", "hello world"}
for _, query := range queries {
// Capture query in closure
q := query
debouncedSearch(func() {
searchAPI(q)
})
time.Sleep(100 * time.Millisecond) // Simulate typing speed
}
// Wait for final search
time.Sleep(500 * time.Millisecond)
// Output: Searching for: hello world
}
Creates a new debounced function.
Parameters:
after
: Duration to wait before executing the functionoptions
: Optional configuration options
Returns: A debounced function that accepts a function to execute
Sets the maximum number of calls before immediate execution.
count
: Maximum number of calls (use -1 for no limit)- Default: No limit (-1)
Sets the maximum wait time before immediate execution.
limit
: Maximum duration to wait- Default: No limit
When multiple calls are made quickly, only the last function provided will be executed:
debounced := debounce.New(100 * time.Millisecond)
debounced(func() { fmt.Println("First") })
debounced(func() { fmt.Println("Second") })
debounced(func() { fmt.Println("Third") })
// Output: Third
The debounced function executes immediately when any of these conditions are met:
- Call limit reached: Number of calls >=
WithMaxCalls
value - Time limit reached: Time elapsed >=
WithMaxWait
value - Natural debounce: No new calls for the specified
after
duration
After execution, all counters and timers are reset:
debounced := debounce.New(100*time.Millisecond, debounce.WithMaxCalls(2))
// First batch
debounced(func() { fmt.Println("First execution") })
debounced(func() { fmt.Println("First execution") }) // Executes immediately
// Second batch - counters are reset
debounced(func() { fmt.Println("Second execution") })
debounced(func() { fmt.Println("Second execution") }) // Executes immediately again
This library is fully thread-safe and can be used safely across multiple goroutines:
debounced := debounce.New(100 * time.Millisecond)
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
debounced(func() {
fmt.Printf("Executed from goroutine %d\n", id)
})
}(i)
}
wg.Wait()
- Search inputs: Debounce API calls while user types
- Button clicks: Prevent double-clicks and rapid submissions
- File watchers: Batch file system events
- Auto-save: Delay saving until user stops typing
- Resize events: Throttle expensive layout calculations
- API rate limiting: Control request frequency
- Batch processing: Collect operations before execution
The debounce implementation uses:
- Mutex for thread safety
- Timer for scheduling
- Minimal memory allocation
- No external dependencies
Benchmark results on typical hardware:
- ~100ns per debounced call
- Constant memory usage regardless of call frequency
- Scales linearly with number of concurrent debouncers
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.