RxJS pairwise Operator

Understand how the RxJS pairwise operator groups the previous and current emissions so values can be compared.

Lesson content

What is the RxJS pairwise Operator? The pairwise() operator groups consecutive Observable emissions into an array containing the previous value and the current value. Simple Definition pairwise() = [Previous Value, Current Value] Basic Example import { from, pairwise } from 'rxjs'; from([ 10, 20, 30, 40 ]) .pipe( pairwise() ) .subscribe( value => console.log(value) ); Output [10, 20] [20, 30] [30, 40] How pairwise Works Source Values: 10 → 20 → 30 → 40 pairwise Output: [10, 20] ↓ [20, 30] ↓ [30, 40] The current value becomes the previous value for the next emission. Important Behavior The operator does not emit when the source produces its first value because no previous value exists yet. First Value = Stored Second Value = First pair emitted Example of(100) .pipe( pairwise() ) .subscribe( value => console.log(value) ); Output No output Previous and Current Values numbers$ .pipe( pairwise() ) .subscribe( ([ previous, current ]) => { console.log( 'Previous:', previous ); console.log( 'Current:', current ); } ); Calculate the Difference import { from, map, pairwise } from 'rxjs'; from([ 100, 130, 125, 160 ]) .pipe( pairwise(), map( ([ previous, current ]) => current - previous ) ) .subscribe( difference => console.log( difference ) ); Output 30 -5 35 Detect Increase or Decrease price$ .pipe( pairwise(), map( ([ previous, current ]) => ({ previous, current, direction: current > previous ? 'increased' : current < previous ? 'decreased' : 'unchanged' }) ) ); Result Example { previous: 100, current: 120, direction: 'increased' } Angular Form Value Example this.searchControl .valueChanges .pipe( pairwise() ) .subscribe( ([ previousValue, currentValue ]) => { console.log( 'Previous search:', previousValue ); console.log( 'Current search:', currentValue ); } ); The first form change is stored, and the operator starts emitting from the second change. Include an Initial Value Use startWith() when the first real emission should be compared with an initial value. import { pairwise, startWith } from 'rxjs'; this.searchControl .valueChanges .pipe( startWith( this.searchControl.value ), pairwise() ) .subscribe( ([ previous, current ]) => { console.log( previous, current ); } ); Flow Initial Value ↓ First Real Change ↓ [ Initial Value, First Real Change ] Route Change Example this.router.events .pipe( filter( event => event instanceof NavigationEnd ), map( event => ( event as NavigationEnd ).urlAfterRedirects ), pairwise() ) .subscribe( ([ previousUrl, currentUrl ]) => { console.log( 'From:', previousUrl ); console.log( 'To:', currentUrl ); } ); Scroll Direction Example fromEvent( window, 'scroll' ) .pipe( map(() => window.scrollY ), pairwise(), map( ([ previous, current ]) => current > previous ? 'down' : 'up' ) ) .subscribe( direction => console.log(direction) ); Compare Object Values users$ .pipe( pairwise() ) .subscribe( ([ previousUsers, currentUsers ]) => { const difference = currentUsers.length - previousUsers.length; console.log( 'User count change:', difference ); } ); Detect a Specific Change status$ .pipe( pairwise(), filter( ([ previous, current ]) => previous !== 'completed' && current === 'completed' ) ) .subscribe(() => { console.log( 'Task completed' ); }); pairwise with Signals import { signal } from '@angular/core'; import { toObservable } from '@angular/core/rxjs-interop'; import { pairwise } from 'rxjs'; count = signal(0); countChanges$ = toObservable( this.count ).pipe( pairwise() ); this.countChanges$ .subscribe( ([ previous, current ]) => { console.log( previous, current ); } ); pairwise vs bufferCount pairwise() bufferCount(2, 1) Designed for previous and current values Creates configurable value buffers Always creates pairs Can create groups of different sizes Simple comparison syntax More flexible buffering behavior source$.pipe( pairwise() ); This is similar to: source$.pipe( bufferCount( 2, 1 ) ); pairwise vs distinctUntilChanged pairwise() distinctUntilChanged() Emits previous and current values Prevents duplicate consecutive values Used to compare changes Used to ignore unchanged values Output is a tuple Output remains a single value Example Together value$ .pipe( distinctUntilChanged(), pairwise() ) .subscribe( ([ previous, current ]) => { console.log( previous, current ); } ); pairwise vs scan pairwise() scan() Stores only the previous value Maintains accumulated state Outputs two consecutive values Outputs the accumulated result Used for direct comparison Used for totals, state, and reducers Common Use Cases Compare previous and current form values Detect price increases or decreases Track previous and current routes Detect scrolling direction Compare application state changes Calculate differences between values Detect transitions between statuses Common Mistakes 1. Expecting an Output After One Emission The operator requires two source emissions before producing its first output. 2. Forgetting an Initial Value Use startWith() when the first real value must be compared against an initial value. 3. Mutating Previous Objects When the source emits the same mutable object reference repeatedly, both values may represent the same mutated object. Avoid this.user.name = 'Rahul'; this.userSubject.next( this.user ); Prefer this.user = { ...this.user, name: 'Rahul' }; this.userSubject.next( this.user ); 4. Using pairwise for Complete History The operator stores only the previous value, not every historical emission. 5. Comparing Large Objects Repeatedly Compare only the required properties instead of performing expensive deep comparisons for every emission. Best Practices Use pairwise() when both previous and current values are required Destructure the pair into meaningful variable names Use startWith() when an initial comparison is needed Use immutable object updates Compare only the necessary properties Combine it with map() to calculate differences Combine it with filter() to detect specific transitions Interview Questions What does pairwise() do? It groups consecutive source emissions into a tuple containing the previous value and the current value. [ previous, current ] Does pairwise emit the first source value? No. It waits until the source has emitted at least two values. How can the first value be compared with an initial value? Add startWith() before pairwise() . source$.pipe( startWith(initialValue), pairwise() ); What is the difference between pairwise and distinctUntilChanged? pairwise() provides both consecutive values. distinctUntilChanged() prevents duplicate consecutive values from being emitted. Does pairwise store the complete emission history? No. It stores only the immediately previous value. Key Point The pairwise() operator makes it easy to compare each current Observable value with the value emitted immediately before it. Easy Formula Source: A → B → C → D pairwise(): [A, B] [B, C] [C, D] pairwise() = Previous Value + Current Value First Output = After Two Emissions Initial Comparison = startWith() + pairwise()