Overview
The delayWhen operator delays each emission from the source Observable by a time span determined by another Observable. Unlike delay, which uses a fixed duration, delayWhen allows you to dynamically compute the delay for each value.
The source value is emitted only when the “duration” Observable emits a next value. Completion alone does not trigger emission.
Signature
function delayWhen < T >(
delayDurationSelector : ( value : T , index : number ) => ObservableInput < any >
) : MonoTypeOperatorFunction < T >
// Deprecated signature
function delayWhen < T >(
delayDurationSelector : ( value : T , index : number ) => ObservableInput < any >,
subscriptionDelay : Observable < any >
) : MonoTypeOperatorFunction < T >
Parameters
delayDurationSelector
(value: T, index: number) => ObservableInput<any>
required
A function that returns an Observable (the “duration” Observable) for each value emitted by the source. The emission of that value is delayed until the duration Observable emits a next value. Parameters:
value: The emitted value from source
index: Zero-based emission index
Returns: An ObservableInput that controls when the value is emitted
subscriptionDelay
Observable<any>
deprecated
Deprecated in v8. An Observable that triggers subscription to the source once it emits any value.
Returns
return
MonoTypeOperatorFunction<T>
A function that returns an Observable that delays emissions based on the ObservableInput returned by delayDurationSelector.
Usage Examples
Random Delay
Delay each click by a random amount of time:
Random delays
Increasing delays
import { fromEvent , delayWhen , interval } from 'rxjs' ;
const clicks = fromEvent ( document , 'click' );
const delayedClicks = clicks . pipe (
delayWhen (() => interval ( Math . random () * 5000 ))
);
delayedClicks . subscribe ( x => console . log ( x ));
// Each click delayed by 0-5 seconds randomly
Value-Based Delay
Delay based on the emitted value:
import { of , delayWhen , timer } from 'rxjs' ;
interface Task {
id : number ;
priority : 'high' | 'medium' | 'low' ;
}
const tasks : Task [] = [
{ id: 1 , priority: 'low' },
{ id: 2 , priority: 'high' },
{ id: 3 , priority: 'medium' }
];
const priorityDelays = {
high: 0 ,
medium: 1000 ,
low: 3000
};
from ( tasks ). pipe (
delayWhen ( task => timer ( priorityDelays [ task . priority ]))
). subscribe ( task => {
console . log ( `Processing task ${ task . id } ( ${ task . priority } )` );
});
// Output:
// Processing task 2 (high) - immediate
// Processing task 3 (medium) - after 1s
// Processing task 1 (low) - after 3s
Network Request Delay
Delay based on API response:
import { fromEvent , delayWhen , ajax } from 'rxjs' ;
import { map } from 'rxjs/operators' ;
const clicks$ = fromEvent ( button , 'click' );
clicks$ . pipe (
delayWhen (() =>
ajax . getJSON ( '/api/rate-limit' ). pipe (
map ( response => response . retryAfter )
)
)
). subscribe (() => {
console . log ( 'Action performed after checking rate limit' );
});
Conditional Delays
Apply different delays based on conditions:
import { interval , delayWhen , timer } from 'rxjs' ;
import { take } from 'rxjs/operators' ;
interval ( 500 ). pipe (
take ( 10 ),
delayWhen (( value , index ) => {
// Even numbers: delay 2s, odd numbers: delay 500ms
const delay = value % 2 === 0 ? 2000 : 500 ;
return timer ( delay );
})
). subscribe ( value => {
console . log ( ` ${ Date . now () } : ${ value } ` );
});
How It Works
Important: In RxJS v7+, the duration Observable must emit a next value to trigger the delayed emission. Completion alone is not sufficient.
For each value emitted by the source:
delayDurationSelector is called with the value and index
The returned Observable becomes the “duration” Observable
RxJS subscribes to the duration Observable
When the duration Observable emits a next value:
The source value is emitted to subscribers
The duration Observable is unsubscribed
If the duration Observable only completes (without nexting), the value is never emitted
If the duration Observable errors, the error propagates to output
Important Behavior Changes
Breaking change in RxJS v7: Previously, completion of the duration Observable would trigger emission. Now, only next notifications trigger emission. Use timer(delay) or observables that emit values, not just complete.
// ❌ This will NOT work (never emits)
delayWhen (() => EMPTY ) // EMPTY only completes
// ✅ This works correctly
delayWhen (() => timer ( 1000 )) // timer emits then completes
Common Use Cases
Variable Rate Limiting : Different delays based on user role or subscription tier
Adaptive Throttling : Delay based on server load or network conditions
Priority Queues : Process high-priority items first with shorter delays
Backoff Strategies : Implement exponential backoff for retries
Coordinated Timing : Synchronize emissions with external events
Implementation Details
The operator is implemented using mergeMap:
export function delayWhen < T >(
delayDurationSelector : ( value : T , index : number ) => ObservableInput < any >
) : MonoTypeOperatorFunction < T > {
return mergeMap (
( value , index ) =>
rx (
delayDurationSelector ( value , index ),
take ( 1 ), // Only first emission matters
map (() => value ) // Return original value
) as Observable < T >
);
}
Each emitted value creates a new subscription to a duration Observable
For high-frequency sources, this can create many concurrent subscriptions
Consider using mergeMap with concurrency limit if needed
Ensure duration Observables complete to avoid memory leaks
Common Patterns
Exponential Backoff
import { delayWhen , timer , retry } from 'rxjs' ;
function exponentialBackoff ( maxRetries : number ) {
return retry ({
count: maxRetries ,
delay : ( error , retryCount ) => timer ( Math . pow ( 2 , retryCount ) * 1000 )
});
}
Stagger with Index
import { from , delayWhen , timer } from 'rxjs' ;
from ([ 1 , 2 , 3 , 4 , 5 ]). pipe (
delayWhen (( _ , index ) => timer ( index * 200 ))
). subscribe ( console . log );
// Staggered emissions every 200ms
delay - Fixed delay for all emissions
debounce - Debounce with dynamic duration
throttle - Throttle with dynamic duration
mergeMap - Map to inner observables
timer - Create delayed Observable
See Also