Overview
Emits a notification from the source Observable only after a particular time span determined by another Observable has passed without another source emission.
debounce delays notifications emitted by the source Observable, but drops previous pending delayed emissions if a new notification arrives on the source Observable.
Type Signature
function debounce < T >(
durationSelector : ( value : T ) => ObservableInput < any >
) : MonoTypeOperatorFunction < T >
Parameters
durationSelector
(value: T) => ObservableInput<any>
required
A function that receives a value from the source Observable, for computing the timeout duration for each source value, returned as an Observable or a Promise. The duration Observable determines how long to wait before emitting. If a new value arrives before the duration completes, the previous pending emission is cancelled.
Returns
MonoTypeOperatorFunction<T> - A function that returns an Observable that delays the emissions of the source Observable by the specified duration Observable, and may drop some values if they occur too frequently.
How It Works
When a value arrives from the source, it’s cached
A duration Observable is created by calling durationSelector(value)
If a new value arrives before the duration Observable emits:
The previous pending emission is cancelled
The process restarts with the new value
When the duration Observable emits , the cached value is emitted
If the source completes during a pending duration, the cached value is emitted before completion
If an error occurs during the scheduled duration or after it, only the error is forwarded. The cached notification is not emitted.
Usage Examples
Basic Example: Emit After Burst of Clicks
import { fromEvent , scan , debounce , interval } from 'rxjs' ;
const clicks = fromEvent ( document , 'click' );
const result = clicks . pipe (
scan ( i => ++ i , 1 ),
debounce ( i => interval ( 200 * i ))
);
result . subscribe ( x => console . log ( x ));
// Wait time increases with each click in the burst
import { fromEvent , debounce , timer , map } from 'rxjs' ;
const input = document . querySelector ( '#search' ) as HTMLInputElement ;
const search$ = fromEvent ( input , 'input' ). pipe (
map ( e => ( e . target as HTMLInputElement ). value ),
debounce ( term => {
// Longer delay for shorter terms (likely still typing)
const delay = term . length < 3 ? 1000 : 300 ;
return timer ( delay );
})
);
search$ . subscribe ( term => {
console . log ( 'Search for:' , term );
});
Dynamic Debounce Based on Value
import { fromEvent , debounce , timer , map } from 'rxjs' ;
interface FormData {
field : string ;
value : string ;
priority : 'high' | 'low' ;
}
const formChanges$ = fromEvent < Event >( document , 'input' ). pipe (
map ( e => {
const target = e . target as HTMLInputElement ;
return {
field: target . name ,
value: target . value ,
priority: target . dataset . priority as 'high' | 'low'
};
}),
debounce ( data => {
// High priority fields debounce faster
const delay = data . priority === 'high' ? 300 : 1000 ;
return timer ( delay );
})
);
formChanges$ . subscribe ( data => {
console . log ( 'Validate field:' , data . field , data . value );
});
API Search
Form Validation
import { fromEvent , debounce , timer , map , distinctUntilChanged , switchMap } from 'rxjs' ;
import { ajax } from 'rxjs/ajax' ;
const searchInput = document . querySelector ( '#search' ) as HTMLInputElement ;
const searchResults$ = fromEvent ( searchInput , 'input' ). pipe (
map ( e => ( e . target as HTMLInputElement ). value ),
distinctUntilChanged (),
debounce ( term => {
// Adaptive delay based on term length
if ( term . length === 0 ) return timer ( 0 );
if ( term . length < 3 ) return timer ( 500 );
return timer ( 300 );
}),
switchMap ( term => {
if ( ! term ) return [];
return ajax . getJSON ( `/api/search?q= ${ encodeURIComponent ( term ) } ` );
})
);
searchResults$ . subscribe ( results => {
console . log ( 'Search results:' , results );
});
When to Use
Use debounce when:
You need dynamic delay based on the emitted value
Implementing search-as-you-type with adaptive timing
Form validation with different delays per field
Waiting for user to finish an action before processing
Don’t use debounce when:
You need a fixed delay (use debounceTime instead)
You want to emit at regular intervals (use auditTime or throttleTime)
You need the first value in a burst (use throttle)
Every value is important (consider buffer instead)
Common Patterns
Adaptive Search Delay
import { fromEvent , debounce , timer , map } from 'rxjs' ;
const searchInput = document . querySelector ( '#search' ) as HTMLInputElement ;
const search$ = fromEvent ( searchInput , 'input' ). pipe (
map ( e => ( e . target as HTMLInputElement ). value ),
debounce ( term => {
// No delay for empty (clearing search)
if ( ! term ) return timer ( 0 );
// Longer delay for short terms (likely still typing)
if ( term . length < 3 ) return timer ( 800 );
// Short delay for longer terms
return timer ( 300 );
})
);
search$ . subscribe ( term => performSearch ( term ));
function performSearch ( term : string ) {
console . log ( 'Searching for:' , term );
}
Priority-Based Validation
import { merge , debounce , timer , map } from 'rxjs' ;
const criticalFields$ = getCriticalFieldChanges (). pipe (
debounce (() => timer ( 200 )) // Fast validation
);
const normalFields$ = getNormalFieldChanges (). pipe (
debounce (() => timer ( 800 )) // Slower validation
);
const allValidations$ = merge ( criticalFields$ , normalFields$ );
allValidations$ . subscribe ( field => validateField ( field ));
Combine debounce with distinctUntilChanged to avoid processing duplicate values when the user corrects a typo back to the previous value.
Behavior on Completion
import { of , debounce , timer , delay } from 'rxjs' ;
// Source completes during pending debounce
const source$ = of ( 'a' , 'b' , 'c' );
const result$ = source$ . pipe (
debounce (() => timer ( 100 ))
);
result$ . subscribe ({
next : val => console . log ( 'Value:' , val ),
complete : () => console . log ( 'Complete' )
});
// Output:
// Value: c (after 100ms)
// Complete
debounceTime - Same behavior but with fixed duration
audit - Emits last value after duration, doesn’t require silence
throttle - Emits first value and ignores subsequent ones
distinctUntilChanged - Often combined to filter duplicates
switchMap - Often used after debounce for API calls